Engagement overview
The client operates a multi-tenant platform: an agency builds, manages, and publishes websites for its customers through a central application, with a separate rendering service serving each published site under its own domain.
The platform was approaching the point where it would begin holding real customer data, and the owner commissioned an independent assessment to answer the question every platform owner eventually has to ask honestly instead of hopefully:
What has to be true before this platform can responsibly hold customer data?
Not "do we think we’re secure," which is a feeling, but a specific, falsifiable list of conditions, each with an owner and a way to verify it is actually met. Most engagements quietly answer a smaller question than the one that was asked: a scan tells you about the app, a pentest tells you about the app plus whatever is reachable from outside it, and neither tells you whether the operation around the app, deployment, credentials, backups, incident response, is something you would want to bet customer data on.
Answering the real question took three completed phases and a fourth planned one. External reconnaissance established what an unauthenticated attacker could discover and reach. Live penetration testing examined authentication, authorization, tenant isolation, upload handling, business logic, and infrastructure exposure. A source-informed threat model then evaluated the architecture behind the observed behaviour: credentials, trust boundaries, deployment provenance, destructive attack paths, backup and recovery, monitoring, and incident readiness.
One discipline ran through all of it: evidence strength was kept separate from severity. A finding that is verified live and a finding that is inferred from source but unconfirmed in production can carry identical severity numbers and completely different levels of trust, and a report that blurs the two is quietly asking the reader to trust its confidence along with its content. So every material claim was tagged as one of:
Recorded evidence strength:
- Verified from source or configuration
- Verified through live authorized testing
- Client-confirmed
- Inferred from available evidence
- Unknown and requiring additional evidence
Controls that could not be proven were not presented as facts, and hypotheses that did not survive testing were written up as ruled out rather than quietly dropped, because the client deserves to know a scary-sounding theory was actually checked and closed, not just that it went unmentioned.
The result was not only a vulnerability assessment. It was a production-readiness decision backed by evidence, a target-state security architecture, and a prioritized engineering plan for changing that decision.
Phase timeline
External reconnaissance
CompleteMapped the observable attack surface across DNS, TLS, exposed services, application hosts, object storage, monitoring endpoints, and proxy-bypass paths. This phase established what an unauthenticated attacker could discover and reach before application credentials or source access were introduced.
The work covered:
- DNS and hostname discovery
- TLS posture
- Public and out-of-band services
- Reverse-proxy behaviour
- Direct-port exposure
- Storage endpoints
- Monitoring and infrastructure services
- Initial attack-path hypotheses
The resulting attack-surface map shaped the authenticated and source-informed phases that followed.
Authorized penetration testing
CompletePerformed live, authorized, non-destructive testing of the deployed platform.
Coverage included:
- Authentication
- Session handling
- Horizontal and vertical authorization
- Cross-tenant access
- IDOR
- Privilege escalation
- Mass assignment
- CSRF
- Rate limiting
- File upload
- Input injection
- Renderer behaviour
- Business logic
- Infrastructure exposure
The testing also verified remediation of earlier critical findings and confirmed which application-layer controls remained effective under adversarial use rather than source inspection alone.
Test data was restricted to isolated tester-owned projects. No pre-existing tenant data was modified or deleted.
Threat modelling and security architecture
CompletePerformed an evidence-disciplined static assessment against a pinned source revision and the available infrastructure configuration.
The work covered:
- Architecture reconstruction
- Asset identification
- Security invariants
- Trust boundaries
- Data-flow modelling
- STRIDE analysis
- Attack trees
- Authentication and session design
- Tenant-isolation architecture
- Credential blast radius
- Object-storage and database privileges
- Renderer and browser attack paths
- Deployment provenance
- Supply-chain risk
- Backup and ransomware exposure
- Failure modes
- Detection and incident readiness
- Assume-breach scenarios
The assessment concluded with:
- A target-state security architecture
- Ten proposed architecture decisions
- Implementation-ready remediation specifications
- Migration and rollback considerations
- Security regression-test requirements
- Detection and incident-response specifications
- Objective closure criteria for each material finding
Incident-response and recovery validation
PlannedOnce the backup and recovery architecture has been implemented, conduct a controlled exercise covering:
- Clean-environment restoration
- Destructive data-loss scenarios
- Credential rotation
- Compromised-host response
- Evidence preservation
- Incident ownership and escalation
- Recovery sequencing
- Measured RPO and RTO
- Post-exercise remediation
The objective is to verify that the new controls function under realistic failure conditions rather than existing only as documentation.
Methodology
The engagement combined black-box, authenticated, white-box, and architectural analysis, because each answers a question the others cannot. A source review that finds a beautiful authorization check tells you nothing about whether that check is what is actually running in production; a pentest that finds nothing tells you about the paths it tried, not the architecture underneath them.
What can an unauthenticated attacker discover and reach?
Which reachable paths can be exploited in the deployed application?
How are identities established, maintained, revoked, and protected?
Can a user cross project, tenant, role, or object boundaries?
What security behaviour is implemented in the reviewed revision?
How can individual weaknesses combine across trust boundaries?
What authority does an attacker inherit after compromising an application process, service account, or operator identity?
Can the platform detect, contain, and recover from a destructive event?
Running all of these together is what let the engagement say something a single-method assessment structurally cannot: not just "here are some bugs," but "here is what would have to be true for this platform to be a defensible place to put customer data." It also kept source-level assumptions from being mistaken for production facts, and kept live testing from being read as complete coverage of architectural and operational risk.
Selected findings
anonymized · exploit details withheld · click to expand
The interesting result was not the bug count, it was where the risk actually lived. Authentication and tenant isolation, the parts everyone assumes are the scary part of a multi-tenant CMS, held up under live testing. What actually blocked the platform from holding customer data was almost entirely operational: no independent recovery path, a production build that could not be traced to a reviewed commit, and credentials with far more authority than the application ever needed. The findings below show the range of the assessment; they are not the complete risk register.
BACKUP-001High 15No production backup or disaster-recovery capability
The platform’s internal "backup" feature was page-version snapshots living inside the same database they were supposedly backing up. That is version history, genuinely useful for undoing a bad edit, and it is not disaster recovery. Treating one as the other is the single most common category error in this kind of review: mistaking an in-band convenience feature for an out-of-band safety net.
At the time of assessment, no independent off-box backup existed for the primary database or object storage, so database loss, storage loss, ransomware, destructive compromise, or host failure had no recovery path at all.
The risk was amplified by:
- Hard-delete code paths
- Fully privileged database credentials
- Root-level object-storage credentials
- No independent recovery account
- No tested restore procedure
A single ransomware event, destructive intrusion, infrastructure failure, or operator error could result in permanent and unrecoverable loss of all tenant data.
No control with disaster-recovery value existed at the time of assessment.
- Off-box database backups
- Independent object-storage replication
- Separate backup credentials
- Immutable or versioned storage
- Encryption
- Defined retention
- Backup-failure alerting
- Scheduled clean-environment restore drills
The finding cannot be closed merely because backup files exist. Closure requires a successful restore into a clean environment, verification of platform functionality, and evidence that the documented recovery objective can be met under realistic conditions. A backup nobody has restored from is a hypothesis, not a control.
DEPLOY-001High 12Running production build not attributable to a reviewed commit
The running production build could not be reliably traced back to a specific reviewed commit. That sounds like a process gap; it is actually a foundation crack under every other finding in the report, because a source review’s authority rests entirely on the assumption that the reviewed source is what is running.
The deployment used manually built images and mutable references, the health endpoint exposed no build identity, and the live environment behaved differently from the reviewed source in both directions. Break that attribution and every "this control is present in source" claim quietly downgrades to "present in a version of the source, unclear which one, unclear if it is this one." Incident response suffers for the same reason: you cannot roll back to a known-good state you cannot identify.
The client could not prove:
- Which code was running
- Whether a source-reviewed control existed in production
- Whether a deployed fix was included
- Which artifact was known-good
- Which version should be used during rollback
- Whether a registry or build compromise had changed the runtime
- Build from a clean and approved commit
- Record the full commit SHA
- Generate an immutable image digest
- Sign and verify the digest
- Deploy by digest rather than mutable tag
- Expose build metadata through an operational endpoint
- Record the deployment operator and timestamp
- Retain the previous known-good digest
Every running production artifact must map to an approved repository commit, a recorded build, an immutable registry digest, and a named deployment event.
EDGE-001High 12ConditionalProduction served an internal self-signed TLS certificate
The deployed platform used an internally issued certificate rather than a publicly trusted production certificate.
The rating depends on the intended operating model.
If the service is deliberately private and every managed client securely trusts the organizational CA, the condition may be acceptable. If the platform is intended to serve public client websites, it is a release-blocking control failure.
Users cannot establish a normally trusted browser connection. Certificate warnings train users to bypass trust failures, and security controls such as HSTS cannot be safely relied upon until the certificate path is correct.
- Replace the internal certificate with a valid production certificate
- Confirm normal browser and system trust
- Verify renewal
- Enable HSTS only after trusted HTTPS is stable
- Prevent direct application ports from bypassing the edge
CRED-001 / CRED-002Medium 10 eachApplication used administrative database and object-storage identities
The application ran with administrative database and object-storage identities rather than role-scoped runtime credentials: a root-level object-storage credential and a database-owner identity on PostgreSQL.
This is the exact failure mode where infrastructure consolidation quietly becomes identity consolidation. An application process that can also drop schema, administer storage policy, and reach backup data is not an application process anymore, it is an administrative control plane wearing an application’s clothes. A compromise would inherit that authority, not just the data operations normal runtime needs.
A compromised application process could potentially:
- Read or modify data across all tenants
- Delete production content
- Change storage policy
- Create additional service credentials
- Alter or destroy database objects
- Expand persistence
- Amplify the consequences of the missing backup system
Introduce separate identities for:
- Database ownership
- Database migration
- Application runtime
- Background workers
- Logical backup
- Object-storage runtime
- Object-storage administration
- Backup replication
- Restore operations
TENANT-001Medium 8Cross-tenant write path in the shared preset library
An authenticated user could supply an arbitrary project identifier when creating a reusable block preset, permitting unauthorized writes into another project’s preset library or the global shared library. Reads stayed scoped, so this was not direct access to another tenant’s content.
Individually it is a smaller finding, but it is a good illustration of why shared code needs to be scoped at the helper level rather than trusted at each call site: a single unscoped lookup reused across contexts turns a local mistake into a platform-wide one.
The issue created a cross-tenant integrity violation:
- Unauthorized content pollution
- Shared-library manipulation
- Potential workflow disruption
- Loss of confidence in tenant boundaries
The wider authorization architecture remained materially sound. The issue was isolated to one write path that performed authentication without enforcing project-scoped authorization before persistence.
- Require an explicit project capability on the exact target project
- Allow global preset creation only for platform administrators
- Complete authorization before any database insert
- Return a deterministic denial
- Add negative tests proving that unauthorized requests create no record
Controls independently validated
Controls that held up deserve the same rigor as controls that did not. A report that only lists what is broken gives you half the picture, and quietly pressures a team into believing nothing they built was any good. The application-layer controls below were verified through source review and, where applicable, challenged through live authorized testing, not just read off the source.
Project authorization was derived server-side from database-backed membership rather than from client-supplied user, tenant, or role values.
Cross-tenant object access, privilege escalation, and mass-assignment attempts failed during live testing.
No general tenant-isolation collapse was identified.
Application queries were constructed through parameterized ORM operations rather than attacker-controlled SQL concatenation.
Source tracing found no exploitable raw-query path, and live injection probes did not produce database-level execution.
The upload pipeline used:
- File-type allowlisting
- Magic-byte signature verification
- Size validation
- Type revalidation
- Object metadata checks
Verified controls included:
- Secure cookies
- HttpOnly cookies
- SameSite protection
- Limited session lifetime
- Session revocation after password reset
- Stronger password requirements
- Rate limiting
- Modern password hashing
Several malformed or unauthorized requests returned imperfect error responses, including internal server errors where a cleaner denial would have been preferable.
However, the tested routes failed closed and did not return unauthorized tenant data.
This distinction was recorded: poor error handling was reported separately from an authorization bypass.
Architecture and trust boundaries
Public application traffic was supposed to enter through the edge proxy. Live testing found deployment drift and out-of-band services that bypassed that intended boundary entirely; those exceptions were recorded separately as infrastructure and deployment findings.
The platform’s primary operational weakness was recoverability: production data in the database and object storage had no independent off-box recovery copy (BACKUP-001).
The complete assessment included:
- Full data-flow diagrams
- Trust-boundary analysis
- Attack trees
- Credential-flow modelling
- Assume-breach paths
- Target-state architecture
- Migration requirements
Deliverables and outcome
- External reconnaissance and security-audit report
- Follow-up verification and penetration-test report
- 135-page threat model and attack-surface assessment
- Risk register with scored and evidence-classified findings
- Data-flow diagrams and attack trees
- Target-state security architecture
- Ten proposed architecture decisions
- Implementation-ready remediation specifications
- Security regression-test requirements
- Acceptance criteria and closure procedures
- Phased remediation roadmap
The engagement produced a connected chain, not four disconnected documents.
- The reconnaissance report established what was externally reachable.
- The penetration test verified what was actually exploitable and confirmed which earlier fixes held under adversarial pressure.
- The threat model tied all of that back to source code, trust boundaries, credentials, deployment, recovery, and failure modes.
- The target-state architecture turned the resulting risk into implementation-ready engineering work, with acceptance criteria attached to every material finding.
The platform was not yet ready to hold customer data, but the reason was not a collapse of its application security model.
The application layer was genuinely solid: tenant isolation and authorization held up under real adversarial pressure, not just a source read.
What actually stood between it and customer data was recoverability, deployment provenance, and credential scope, the operational half of the system that is easy to underinvest in precisely because it does not fail loudly until the day it does.
So instead of recommending a broad rewrite of a functioning application layer, the engagement identified the smaller set of architectural and operational controls that would actually change the platform’s risk posture, the part of the system that is not the app but that the app’s safety quietly depends on.
The client received:
- Immediate actions for the first seven days
- A 30-day hardening programme
- Longer-term architectural improvements
- Defined implementation owners
- Acceptance and negative-test requirements
- Migration and rollback considerations
- Concrete evidence required to close each finding
The value of a multi-phase, evidence-disciplined assessment is not the finding count. It is converting "do we think this is secure" into "here is exactly what has to be true, who owns making it true, and how we will know when it is."
The planned incident-response and recovery exercise will close the lifecycle by testing whether the implemented backup, containment, and restoration controls actually work under realistic destructive conditions.
Client perspective
“Haroun gave us the confidence to finally take our software live. I care a lot about doing things properly, and what stood out to me was how structured he was the whole way through. He really got to know our system from the inside, focused on the controls that actually mattered, and was careful to separate the weaknesses he could confirm from the ones that were just assumptions. Honestly, without his work we wouldn't have felt ready to go live.
At the end he handed us a clear, prioritized plan for fixing things, a picture of where our security should be heading, and acceptance criteria that were specific enough for our own engineers to implement and check themselves. We'll be sticking with Haroun for our regular testing from here on. Can't recommend him enough.”
Confidential European Software Company
Sample material
The complete reports contain client-confidential architecture, infrastructure, source references, and exploit details and are therefore not published.
The samples below are anonymized and abridged. Client names, product names, domains, addresses, credentials, repository identifiers, source paths, and selected attack steps have been removed or generalized while preserving the assessment methodology, reasoning structure, and deliverable quality.