Case study 01 · Cyber exposure platform · 2021 – present
Five years turning a security consultancy's expertise into a product that runs itself
Ten security services, and one score a director can act on.
Written by Hesham Ghoniem, lead engineer · ·
- Sector
- Cyber security software, sold online direct and under partner brands
- Engagement
- Ongoing since 2021 · design, build, run, maintain
- Scope
- Full platform · 3 production servers we administer
- Stack
- Laravel
- Vue 3
- Inertia
- Tailwind
- MySQL
- PostgreSQL
- Redis
- AWS
- 10 — Security services, five feeding one score
- 10 — External services in use, each health-checked
- 88 — Jobs running on a timer, fastest every 30 seconds
- 5 years — One rebuild live, a second in its final stage
- ~260,000 — Lines of code in the merged application
Overview
They wanted to sell a product instead. We built it.
A security consultancy was selling its knowledge one report at a time. Each engagement produced a document that was accurate on the day it was written and out of date the day after. Growing meant hiring more senior people to repeat the same manual work. The consultancy wanted to sell a product instead.
We built it. Since 2021 we have grown a single website into a platform that keeps checking what a criminal could find out about a business: its websites, its leaked passwords, its staff and its suppliers. It runs ten security services on intelligence pulled from outside providers, sells through partners and resellers under their own branding, and runs 88 timed jobs without anyone pressing a button.
In 2025 we built a second product inside it, a self-service brand sold online. That product has a free trial, card checkout and its own dashboard, which reduces everything to one risk score, with the evidence one click underneath.
Twice in those five years the architecture reached its limit. One rebuild is live and a second is in its final stage, both under a product with paying customers on it, and no customer has lost the service. There has been no feature freeze, no risky switch-over weekend and no quarter lost to not selling.
The challenges
Nine problems, and none of them could be solved by working harder.
Expertise that couldn't scale
Consultancy revenue is capped by consultant hours. The product had to do all the time what a specialist did now and then. It had to query several intelligence sources, reconcile results that disagree, decide what matters, and present it so a non-technical director can act, with no human in the loop.
Many sources, one answer
The platform pulls from scanning engines, breach databases, dark-web indexes, training providers, Microsoft directories and a billing system. They return data in different shapes and fail in different ways. The dangerous part is that a broken API key and a clean customer look identical. The platform had to make sure silence could never be read as safety.
No pause button
The business could never stop selling for three months while engineering caught up. Every change to the architecture had to happen on a live product, in pieces, with every bookmarked URL still working the next morning.
The architecture itself became the security risk
By 2026 the platform ran as two applications sharing one database: a hand-built PHP front end and a Laravel engine behind it. Showing a single page took two round trips across two codebases. The front end was pinned to Bootstrap 4, which reached end-of-life in January 2023 and has published vulnerabilities that will never be patched. It also depended on jQuery, an old browser library with its own record of published vulnerabilities.
For a company that sells security scanning, outdated front-end libraries are a credibility problem. Anyone can download front-end code, because no firewall sits in front of a browser. Any researcher scanning the site could spot the old libraries in seconds, and the business could not answer honestly about them on an enterprise procurement questionnaire.
The product couldn't speak anything but English, and couldn't be made to
Every attempt to add a second language stalled because of how the code was structured. The same sentence could live in three places at once: a native PHP page, a jQuery file that built the text in the browser, and a Blade fragment that the Laravel engine sent back over Ajax. There was no single layer where a translation could be applied. Supporting one extra language meant building and synchronising three separate translation systems. The estimate never justified the cost, and the feature kept being deferred.
Right-to-left languages made it worse. Arabic and Hebrew mirror the whole layout as well as changing the words, and the front end was built on a grid with five years of hand-written, English-first CSS on top of it.
Every customer was ours, then they had to belong to someone else
The platform was built for direct customers: one business, one account, sold by the client. Then the business model changed. The client wanted to sell through partners and resellers, IT providers who bring their own customers, manage them, and present the product as their own.
That changed what a customer is. Every record needed an owner above it. A partner had to see all of their customers and none of anyone else's. A reseller under a partner had to see only its own customers, and the partner had to see the reseller's customers as well. Every screen, report, export and email had to follow those rules, on a live system, while the existing direct customers kept using it.
It also had to look like the partner's product, not ours. A customer signing in should see their provider's logo, receive email from their provider's address, and never learn whose platform it is. And because partners charge their own customers through their own billing systems, they needed a billing file their system could import, not a report they would have to re-type.
A second product, inside the first
In 2025 the client decided to sell a new way: straight to businesses, online, under a brand of its own. A visitor signs up on the product's own website, gets a thirty-day free trial, and pays by card when it ends. There is no salesperson, partner or onboarding call.
This was a new business, and it had to run inside the existing one. It shared the application, the database and the scanners with four other kinds of customer account, and those had to keep working exactly as before. Every screen, permission, scheduled job and email had to know which product it was serving, and a change made for one product could never leak into the other.
One checkout page that has to know which of five customers it's talking to
This was the hardest commercial problem in the platform, and the least visible from outside. In the self-service product, a customer opening Manage Subscription could be in any of five situations:
- on a free trial, choosing a paid plan for the first time
- paying monthly and wanting to add capacity
- paying annually
- coming back after a trial lapsed
- reactivating after a paid plan lapsed
A status field cannot tell these situations apart. To a status check, a monthly customer inside a free first month looks exactly like a trial customer, and the page would show the plan picker as if the plan they already pay for didn't exist. Underneath, the money lives in a subscription-billing platform across two separate billing accounts, and our platform keeps its own record of what each customer is entitled to. The two records must never disagree.
An email that looks right in one inbox and broken in the next
The platform sends 82 different emails, including alerts, monthly reports, renewals and failed-payment notices. Each one has to arrive looking the way it was designed, wherever it is opened.
Email has no common standard the way web pages do. Outlook on Windows draws email with Microsoft Word's engine, which ignores much of modern layout. An email that looks finished on a phone can arrive in a director's Outlook with its layout collapsed, its button missing and its images stretched, and this product's customers read their mail in Outlook. Each email also has to work on a phone, a tablet and a desktop, stay readable in dark mode, and carry each partner's own logo instead of ours.
Early 2024 · the supplier crisis
The week the data stopped
The dark-web service has run on four providers, and we chose only one of those changes.
It started on a public breach-lookup service, which was simple and narrow. When the client wanted richer intelligence, we moved the service to a far more comprehensive provider and rebuilt the module around it. The extra volume made pages slow under the number of records, so we analysed the queries behind them, added the indexes they needed, and the module was fast again.
Then, early in 2024, that provider gave notice that it would shut its service off at the end of the month.
The risk was commercial. Dark-web monitoring was one of the platform's headline services and a main reason customers paid. Losing it meant every customer would open their dashboard to an empty breach module for the months it would take to find, contract, integrate and test a replacement. Customers would have been within their rights to ask for their money back, and some would have left for good.
We investigated instead of panicking, and found the answer in our own data.
Every breached address the outgoing provider had ever found was already stored in our database. We proposed to the client's technical director that we send those addresses back to the breach-lookup service the module had started on. That would keep answering the one question customers cared about: has anything of ours been exposed?
The client approved the proposal, and we built the bridge in under two weeks: a scheduler that worked through every stored address in turn, oldest-checked first, sending one request every few seconds, day and night, to stay inside the provider's rate limit. The old supplier's service went dark at the end of that month. Customers kept receiving breach findings.
A rail of four providers in turn, with a short bridge back to the first between the second and third. Two forced changes are marked: a shutdown notice early in 2024 and a price increase in 2026. One unbroken bar under the whole rail shows customers kept receiving breach findings throughout.
Then we did the replacement properly.
Over the next two months we evaluated the market, chose a specialist dark-web intelligence provider, and built the full integration. It had a different API, a different response shape, a different idea of what a "breach" is, and many more endpoints. That called for a new database structure instead of a translation layer added to the old one: new tables for breaches, search terms and exposed addresses, the historic data backfilled into the new shape, and the departed supplier's tables dropped once nothing depended on them.
Then it happened again, for a different reason.
In 2026 that provider raised its prices by enough to shock the client, and the search started over. This time the constraint ran the other way. The module now held years of breach history that couldn't be migrated, so the database couldn't change at all, and the new provider had to fit the existing schema. The platform translates every result from the fourth provider's five endpoints into the existing breach tables and tags it with its source, so old and new data sit side by side. History loaded from the new provider fires no "new breach" alerts, because none of it is new to the customer.
Three things came out of it
- The bridge kept running after the emergency. It ran beside the new provider as a second, independent check for five more months, and we retired it only after the new integration had proved reliable.
- The schema stopped changing. By the third change the database was the part that stayed the same, and providers had to fit it. A supplier can now be replaced without changing what customers see.
- Customers never saw a change. There were four providers and two forced moves, and the module kept working through every one.
No customer lost access to the service they were paying for, on any day, at any point in any of the changeovers.
What we built
Five years, one rebuild live and a second in its final stage, and data that never moved.
Chapter one · 2021 · Hand-built foundation
The product takes shape
We started with one website for staff training, a support desk, help pages and user management. For the next two years we added about one new service a quarter: weakness scanning, phishing simulations, billing, live alerts, Microsoft 365 and leaked-password monitoring. By then the site was running the business.
Chapter two · 2023 · One feature at a time
A new engine, built inside the running one
The hand-built foundation had reached its limit. Instead of rebuilding beside it and hoping the switch-over went well, we set up a Laravel application against the same database and moved responsibility across one feature at a time. The original application kept drawing every screen customers saw, while the new engine took over the logic behind them. By 2025 it was taking more than five times as many code changes as the original. Customers never saw a switch-over, because there wasn't one.
Then the business model changed.
At the end of 2023 the client moved from selling direct to selling through partners and resellers, so a platform built for one kind of customer had to serve three layers of tenant. We placed an ownership hierarchy above every account, with the partner at the top, then the reseller, then the customer, and carried it through users, customers, imports and every query, so the server enforces isolation on each record instead of the interface hiding it. The schema changes went in over three weeks, against the live database, while direct customers carried on as a segment of their own.
Each partner and reseller got a brand identity: logo, support address, email signature and footer, applied to the sign-in and account pages and to every email the platform sends. A reseller inherits its partner's branding unless it sets its own, and partners can open any of their customers' dashboards and see exactly what that customer sees.
Because partners bill their own customers, the platform produces a billing sheet in the columns their billing systems import, listing active accounts only and coding each line by the services that customer runs.
2025 · A product inside the product
A second product, inside the first
The client launched a self-service brand sold straight to businesses online, and we built it inside the running platform instead of beside it. A new kind of account carries the whole product. A sign-up on the product's own website passes through its own billing account into the platform; an email check confirms the address, and the platform creates the account and starts its first scan with nobody touching it.
The new product got things no other customer has
- A new dashboard built around a risk score. Up to five services each score what they find, and those scores combine into one overall score, with an arrow against the last score and the five things most worth fixing.
- A supply-chain module. Customers invite their suppliers to answer a security questionnaire and share their certificates.
- A monthly board-ready report. The server draws its gauges, so the PDF matches the screen.
- Twenty-nine emails of its own, including a thirty-day trial sequence timed to the day.
The trial had to sell the product without giving it away. Trial customers see their real findings: the first rows are readable and the rest are masked on the server, not only blurred on screen. When a subscription lapses, the account drops to a free tier that keeps every finding and its history locked instead of deleted, so coming back only takes a new subscription.
It is still one codebase. About a quarter of the platform's permission rules now check which product a customer belongs to, and more than a dozen scheduled jobs run for the new product alone. Customers of the original platform carried on exactly as before.
Chapter three · 2026 · Not yet cut over · In its final stage
Merging the two halves into one
By 2026, running as two applications had become the security risk in challenge four. Before writing any code, we gave the client a costed written proposal with a business case, risks and open questions, followed by a staged twelve-week plan. The result is a single modern application: 121 screens rebuilt in Vue on 480 shared components, one login and one design system. The security workaround the old split forced on us is deleted instead of maintained.
The merge also solved the language problem. With one place where text is written instead of three, translation became part of the platform instead of a separate project. All 5,351 visible strings now live in one set of files that serves both the server and the browser, and the layout mirrors itself for right-to-left languages. Adding a language now takes two steps and no code changes. Arabic, Hebrew, French and Spanish are set up and waiting for translation; English ships today.
The business got the feature it had wanted for years by removing the thing that made it expensive.
And AI
Built to be trusted
A security scanner reports in identifiers, and a director needs a sentence. So every vulnerability the website scans find now gets a plain-English explanation of what it means, how serious it is and what to do about it, written by a large language model within ten minutes of discovery. Three design decisions matter more than the feature itself:
Grounded
It only talks about findings the customer has
Answers are built from data the platform already holds and already shows elsewhere in the portal. Where it holds no advice for a service, the assistant says so instead of inventing something. In a security product, a confident wrong answer is worse than no answer.
Metered
It can't run up a bill unnoticed
Every request is logged and counted against a daily allowance that belongs to the company, not the seat. A customer who reaches the limit gets a clear message on screen, never a silent failure. Explanations are cached by question, so the second customer with a common vulnerability gets the answer instantly and at no cost.
Swappable
Changing provider takes one setting
In 2026 the platform moved from one AI provider to another by changing one value. The old provider stays selectable in case a rollback is needed.
In review
The newest layer is an assistant beside the dashboard that answers questions about whatever the customer is looking at. A dial switches the same answer between board-level, manager-level and fully technical, and re-answers the question already asked so nobody has to retype it. Alongside it, an AI-written "where you stand" paragraph replaces the old list of top risks on the dashboard.
Throughout
The data stayed put
The 2021 website, the 2023 engine, the second product and the merged application all read and write the same database. Every rebuild moved the code around the data, never the data around the code. That is the clearest evidence that we replaced the foundations without disturbing the product.
Technical deep-dive · the scanning engine
Inside the hardest integration
An open-source, self-hosted reconnaissance engine powers the attack-surface discovery service. It is not a commercial API with a contract and a support line. It is a separate application on its own server, with its own PostgreSQL database of around a hundred tables and its own way of shaping scan data. Nobody designed it to feed another product.
The client hoped for some data, any data. They had no clear way to get scan results out of the engine and into the platform. They knew only what they didn't want: customers signing in to a second application to see their own scans. Getting even basic findings across would have counted as a win.
Run it
First, it had to run.
The engine went onto its own server, behind its own secure web address. The certificate alone took several rounds: the certificate authority couldn't reach the server to validate it, certificates vanished on every restart, renewals wouldn't run by themselves, and the services didn't come back after a reboot. We traced and fixed each one. Then we connected the platform to the engine, so it can start, stop and delete scans for a customer without anyone opening the engine.
Stop it overloading the server
Then it overloaded the server.
Under real scans the engine drove the machine to almost 100% of its resources. The easy answer was a bigger server and a permanently bigger bill. Instead we measured every process and read every log until we found the cause: taking a screenshot of each discovered website launched a full web browser per site, dozens at a time, until the memory ran out. We brought that stage under control and retuned the engine's internal message queue. The load stayed steady, and the server never needed the upgrade.
Tune every profile
Then we tuned every scan profile by hand.
The engine's defaults suit a researcher pointing it at one target, not a platform scanning every customer's domains day and night. We ran each profile on trial targets and adjusted it until it returned useful results at a load the server could carry. Request rates dropped to a third, and the slowest brute-force stage is now capped at five minutes instead of running without a limit. Where the engine crashed on scanner output it didn't expect, we patched the engine itself.
Learn it
Then we learned it completely.
We studied the engine end to end: how it runs a scan, what it writes, in what order, and how every table relates to the others. That understanding was harder than the coding.
Round one
Round one worked, and cost too much.
The first integration collected data on a timetable: 23 scheduled commands, 14 of them sweeping the engine's tables every hour, a hundred rows at a time, to find the few that had changed. The data arrived, and so did a backlog of background work the server could barely carry.
Round two
Round two listens instead of asking.
In 2025 we rebuilt the integration around the engine's own database. It now sends a notice for each new row the moment it is written, and thirteen long-running listeners receive each one and store it. We switched off the hourly sweeps, and the background work shrank to a fraction of what it had been.
Mirror
Under both rounds: we refused to read from the engine live.
The obvious approach is to query the engine's database whenever a customer opens a page. We rejected it. If the engine went down, was upgraded or renamed a column, our customers' screens would break, and a core service of a product they pay for would depend on a component we don't control.
Instead we built a complete, standalone copy of every record inside our own database: the full structure in our own shape, not a cache or a summary. 39 kinds of record across 78 database changes, covering subdomains, endpoints, ports, IP addresses, technologies, directories, WAFs, vulnerabilities and the CVE, CWE, reference and tag details attached to each. The product only ever reads from its own tables.
Interface
Then we rebuilt the interface, an application inside our application.
Customers get the full depth of what the engine produces, as part of our product instead of a link to someone else's tool: 11 screens and 58 components.
- subdomains
- live endpoints
- open ports
- technologies
- directories
- DNS & registration
- per-host screenshots
- certificate health
- attack-surface map
- changes since last scan
The scanning engine's PostgreSQL database feeds the platform's own database through 13 live listeners and 24 scheduled commands, and the customer's browser reads only from the platform. No arrow points back, and nothing connects the browser to the engine.
If the scanning engine disappeared tomorrow, every customer would still see every finding it has ever produced.
Keeping that copy current is the main engineering work
Two mechanisms run side by side:
- 13 Live triggers — Thirteen long-running listener processes, kept alive by a supervisor, receive each record, work out which of our tables it belongs in, link it to its scan and domain, and write it, removing duplicates as they go. A listener that loses its connection reconnects by itself; a record it can't parse goes to the log instead of stopping the process.
- 24 Scheduled reconciliation — Triggers catch new records. Scheduled work catches everything else: updates, backfills, and anything missed while a listener was restarting. 24 scheduled commands do this work, and the fastest runs every 30 seconds.
Some scan data doesn't need a supplier.
The domain information beside the scan, who registered the domain, its DNS records, its open ports and where it is hosted, used to come from a paid data API billed per lookup, and with a large and growing number of domains that bill kept climbing. We replaced it with tools the platform runs itself: an open-source WHOIS library, the server's own DNS lookups, an open-source port scanner and a free location lookup. The same information now arrives with no per-domain charge, and the client cancelled the subscription.
The integration runs one way only. Their database sends data to ours, and ours never depends on theirs being up.
A thin brief · certificate analysis
Two words in, a product out
The brief, in full
"SSL analysis."
There was no specification, no list of checks and no screens. We used the open brief to decide what the service should be.
We researched what a website's security certificate reveals, then studied its raw output field by field: who issued it, what it covers, how it is signed, how strong its key is, whether it is publicly logged, and whether it matches the site serving it. Only then did we write the logic to read and organise it, and design fourteen database tables to hold the result: the certificate, its issuer, its full chain of trust, every domain it covers, and every weakness and warning found in it. It is built on the server's own cryptography library, with no supplier and no fee per check.
Then we made it run across every domain at once.
With thousands of domains in the system, nobody was going to check them by hand. Analysis runs automatically, in small batches every minute, and because it is linked to the recon scan it covers every subdomain the scan discovers, not only the address a customer typed in. A forgotten test server with an expiring certificate gets caught too. Failures are sorted into nine kinds, so a domain that no longer exists is treated differently from a server that was briefly slow, and a host that fails twice is paused instead of retried forever.
Then we made it useful to someone who isn't an expert.
Six families of checks feed a rating from Excellent to Poor, and every finding carries a plain-English explanation of what it means and what to do. Customers set their own expiry reminders, choosing how many days ahead and who to tell, and get a digest of everything expiring in the next 25 days and an alert on the day a certificate lapses. The result feeds the website-discovery score.
The client asked for two words. What came back was a complete service, and it surprised them.
How we work
Nine rules, most of them learned the hard way.
Replace the system while it runs
Set the replacement up against the same data as the original, move across in pieces, keep every saved link working, and let the old system serve until the new one has shown it can do the same job. Then retire the old system completely instead of leaving a spare running just in case.
Big changes go to the client in writing first
The merge arrived as a proposal with a business case, a risk list and open questions, not as a surprise invoice. Decisions of that size belong to the client.
Business rules live in settings
Scoring, pricing, limits, password rules, upload rules and retention periods all live in configuration, so the business can change its own commercial rules without waiting for a deployment.
Suppliers sit behind a stand-in
Where a provider could be replaced, the code talks to a stand-in instead of to the supplier directly. We learned this in 2024, when a supplier switched off with a month's notice. Since then we have replaced three data suppliers without disrupting the features that depend on them.
Assume a supplier will disappear
We treat every external feed as temporary, because one of them was. No feature depends so closely on a provider's data shape that losing the provider means losing the feature.
Emails are built for Outlook first
Every email comes from one shared component library, with an Outlook version of each component where needed. Images are PNGs with their size fixed in the markup, buttons still appear with images switched off, and every scenario is sent to a test inbox so we judge each layout in Outlook itself, not in a browser preview.
Never read live from a system you don't control
Where the product depends on another application's data, we copy it into our own database and read from there. It takes more work up front, and a customer's screen never goes blank because something else went down.
AI that says when it has no answer
Everything the platform generates is based on data the customer already has, capped so it cannot spend a budget unnoticed, and built to say "we hold no advice here" instead of filling the gap. We log the spend without storing a single prompt or answer.
We also run it
Three production servers are under our management, including setup, patching, certificates, backups and releases. The client has one number to call whether the fault is in the software or the server.
Technologies used
Nine layers, and what each one is for.
Application
The core of the platform. Laravel is mature, quick to build with and easy to hire for.
- PHP 8.4
- Laravel 12
Interface
Pages update without a full reload, and every dependency has active security support.
- Vue 3
- Inertia
- Tailwind 4
- Vite
Languages
One set of translation files serves both the server and the browser, and page direction flips by itself for right-to-left languages.
- Laravel translations
- laravel-vue-i18n
- Locale registry
AI
Plain-English explanations of security findings. Changing provider is one setting; spend is capped and logged, and prompts and answers are never stored.
- A large language model
- A fallback provider
- Answer cache
- Per-company quotas
Data
One main database. A second belongs to the scanning engine, and the platform mirrors it record by record. Redis runs the queues and cache.
- MySQL
- PostgreSQL
- Redis
Background work
Nothing heavy happens while a user waits. Scans, syncs and reports run on their own schedule.
- Laravel Horizon
- 88 scheduled tasks
Documents
Board-ready PDF reports, practitioner spreadsheets and evidence bundles.
- mPDF
- TCPDF
- Headless Chrome
- Excel
- CSV
- ZIP
Cloud
File storage, email delivery with bounce tracking, and live updates in the browser.
- AWS S3
- Transactional email
- Real-time messaging
Security tooling
Covered in full in the security section below.
- TOTP two-factor
- Magic links
- HTML purification
- Content security policy
- 117 permission rules
Automated testing
Automated tests check each change before it reaches production.
Integrations, by what they do
- Self-hosted reconnaissance engine
- Commercial vulnerability scanner
- Dark-web intelligence provider
- Public breach-lookup service
- Security-awareness and phishing platform
- Microsoft 365
- Slack and Microsoft Teams
- Subscription-billing platform
- Transactional email service
- AI provider and its fallback
- AWS S3
- WHOIS, DNS and port scanning, run in-house
Each of the ten external services in active use has its own health check with a live status. An expired key shows up as a red row in an operations panel, not as a customer with no problems.
How the platform works
Six steps, from a domain name to a board-ready report.
Step 1
Connect
A business signs up and adds its domains. It can connect Microsoft 365 to keep the staff list up to date.
Step 2
Discover
The platform maps everything an attacker can see from outside: subdomains, live endpoints, open ports, technologies in use, DNS records, and published staff names and email addresses, with a screenshot of every host. Every record is copied into its own database as it is found, so the results stay available whether or not the scanning engine is running.
Step 3
Watch, all the time
Certificates are checked, weaknesses re-scanned, leaked credentials searched for, and training and phishing results pulled in. The fastest checks run every 30 seconds, new breach alerts fire within five minutes, and a full score recalculation runs every six hours or on demand.
Step 4
Score
In the self-service product, up to five services each score the risk they find on one shared scale. Those combine into one overall score with a trend arrow and a "top five things to fix" list. A service the customer hasn't bought is left out, not counted as zero.
Step 5
Explain
A large language model writes a plain-English explanation of every vulnerability the website scans find, within ten minutes of discovery. It is based on findings the customer has, metered so it can't run up a bill, and cached so common answers are instant. Findings can be published straight to the customer's own Slack or Microsoft Teams channel.
Step 6
Report
Each self-service customer gets a monthly board-ready PDF that brings every active service into one document. The server draws the risk dials, so the printed report matches the screen exactly. Practitioners get spreadsheet exports, and the business gets its own commercial reporting.
Five services, leaked passwords, weaknesses, website discovery, phishing and training, feed one overall risk score with a trend arrow, which goes to the dashboard and to the monthly report.
And the commercial side: Subscriptions and Manage Subscription
Self-service customers manage their own billing inside the product. The Subscriptions page shows what they have, and Manage Subscription is where they change it. That second page works out on the server which of the five customer situations applies and builds itself to match: only a customer who needs to choose a plan sees the plan choice, only a customer entitled to buy add-ons sees them, and if a card is already on file the payment step disappears, turning a three-step checkout into two.
The billing platform is the source of truth for money, and our platform is the source of truth for entitlement. A single reconciliation keeps the two in step so they never drift apart. Plan limits on domains and scan targets are enforced every hour, subscriptions billed by bank transfer or invoice are fully supported, and the lifecycle runs by itself: a sixteen-email, thirty-day trial sequence; renewal and failed-payment emails; a free tier that keeps the account and its history when a subscription lapses; reactivation; and self-service deletion with a seven-day grace period.
Across the whole platform, targets are approved before anything is scanned, and each partner's customers are invisible to every other partner. Suppliers and external assessors can take part through scoped links that need no login, without ever becoming users of the platform.
Results
What the business can do now that it couldn't in 2021.
Sell two products instead of hours
The original platform sells through partners and resellers. The self-service product sells itself online, with a free trial, card checkout, and plans and add-ons the business prices without a code release.
Launch a new business without a new system
The second product has its own brand, website, billing account, dashboard, risk score and emails, and shares one codebase and one database with the first.
Let customers buy and upgrade by themselves
Two checkout paths that used to live in separate places are now one Manage Subscription page, which works out the customer's situation on the server.
Sell through other companies
Partners and resellers run their own branded portfolios and bill their customers from a sheet their own systems import. A customer never sees whose platform they are on.
Run unattended
88 timed jobs run everything from a 30-second data sync to a monthly executive report, with nobody in the loop.
Answer the hard procurement questions
After the merge, every front-end dependency has active vendor support. In 2025 the business could not answer that question honestly.
Sell in another language
A feature that was estimated and abandoned again and again now takes two steps and no code, including right-to-left languages, which the layout handles by itself.
Explain its own findings
Scanner output becomes a sentence a director can act on, with the cost capped and the AI provider changeable with a single setting.
Stop paying for data it can gather itself
Domain information now comes from open-source tools instead of a paid per-lookup API, and the subscription is gone.
Survive losing a supplier
Losing a data provider once would have taken a main service offline for months. A provider is now a replaceable part, and we have replaced providers twice since, both times on a planned schedule.
Stay up when its dependencies don't
Scan results live in the platform's own database, mirrored record by record. If the scanning engine goes offline, the customer-facing service keeps working.
Add a service without rebuilding
Ten services share one scoring engine, one reporting pipeline and one permission model. An eleventh would plug into them instead of becoming a separate project.
The engineering, in numbers
- 10 Security services, five scored
- 10 External services in active use, each health-checked
- 203 Kinds of record in the merged application
- 117 Permission rules, checked on each record
- 88 Scheduled jobs
- 82 Transactional emails in the merged application, all catalogued and previewable
- 470 Database changes applied, and no customer lost the service
- 121 Screens rebuilt in the merged application
- 5,351 Interface strings in the merged application made translatable, from zero
- 39 Scan-engine record types mirrored into our own database
- 13 Live listener processes keeping that mirror current
Honest status
The merged application is built and in its final stage, with 121 screens and 480 components, but it has not yet been cut over to production. Customers use the two-application platform described in chapter two today, and we are still developing it.
Security engineering
A company that sells security scanning has to meet the standard it sells.
Here, security is built into each layer of the platform from the start.
A request passes five gates in order, transport, session, authorisation, input and uploads, before reaching the data.
Input
Nothing a user types is trusted
Wherever a customer can submit formatted text, it passes through a purifier that works from a list of what is permitted, not a list of what is banned. Scripts, embedded frames, inline styles and event handlers are stripped; only a fixed set of tags, styling properties and link types survives.
Uploads
Every uploaded file is checked in full
A 600-line validation pipeline checks the extension, confirms the file really is the type it claims, enforces a size limit and inspects the contents. Spreadsheets are scanned for formula injection, where a cell beginning with = or @ runs a command on the computer of whoever opens it. Filenames are regenerated and image metadata stripped.
Authorisation
Authorisation is checked on every record
117 permission rules and 11 policies sit behind parameterised routes, so an administrator of one company can never see another company's data. Each request is checked before any data is loaded. That stops a common attack on multi-tenant products: changing a number in a URL to reach someone else's record.
Session
Passwords follow the NIST model
Length matters more than symbols: 16 characters for administrators and 14 for users. Dedicated rules reject dictionary words, words specific to the customer's own company, sequences, keyboard walks, seasonal words and years, with leet-speak normalised first, so P@ssw0rd! is caught just like password.
Session
Sessions are protected after login too
Two-factor authentication, passwordless magic links, lockout after repeated failures, one active session per account and an idle lock screen. The login path is timed so a wrong username and a wrong password take the same time to fail.
Transport
Every response, webhook and query is protected
Every response carries a content security policy, strict transport security, and clickjacking, MIME-sniffing, referrer and permissions protections. Incoming provider data is credential-checked and archived before it is parsed, and in the merged application no user input reaches the database as raw SQL.
A block-list is only as current as the last trick someone thought of. So we don't use one.
Related work
Phishing Simulation Platform
Cybersecurity · 2024 – present · Design, build, maintain
2 — Separate phishing engines unified into one workflow
Air Cargo & Customs Platform
Logistics · 2025 – 2026 · Specify, build, deploy
7 days — Empty repository to a live, documented API. Both sides signed the contract on it
Next step
Have a product that has to keep selling while it gets rebuilt underneath?
Work with an engineering team that replaces foundations without a feature freeze, puts the big decisions to you in writing first, and runs the servers as well as the software.
Client anonymised · every figure counted from the source repositories · October 2026