Penetration testing answers a narrow, useful question: given time and intent, what could someone actually do to this system? Not what a scanner flags as theoretically possible — what is genuinely reachable and exploitable in your environment.
We test web applications, APIs, cloud environments, internal networks and AI deployments, and we report in a way that a development team can act on rather than a PDF that gets filed.
What We Test
- Web applications — authentication and session handling, authorization and access control, injection, business-logic flaws.
- APIs — broken object-level authorization, mass assignment, rate limiting, token handling.
- Cloud environments — identity and permission misconfiguration, exposed storage, network boundaries.
- Internal networks — lateral movement, credential exposure, segmentation that exists on the diagram but not in reality.
- AI and agent deployments — prompt injection, tool abuse and permission escape. See AI and MCP security.
Authorized Engagements Only
All testing is scoped and authorised in writing before anything begins, with agreed rules of engagement, testing windows and escalation contacts. We do not test systems you do not own or have contracted us to examine, and we will not proceed on verbal authorisation.
What You Get
Findings ranked by real exploitability rather than raw severity, with evidence showing how each was reached, clear remediation guidance written for engineers, an executive summary that is honest about overall posture, and a retest once fixes are in. We would rather report five findings you will actually fix than fifty you will not.
If a test would touch operational technology or building systems, that requires a different approach entirely — see OT and ICS security assessment.
Tell us what you need tested and when and we will scope an engagement.
Related services
- Secure Software Development — Fixing what testing finds: secure development lifecycle.
- DevSecOps — Catching it earlier next time: DevSecOps.
When a Penetration Test Is Not the Right Next Step
A test is worth running when you do not already know the answer. If you are sitting on an inventory of unpatched hosts you already have a list of, authentication issues raised in an earlier review and never closed, or a service you know is internet facing without access control in front of it, a test will confirm all of that and spend the engagement doing so. In that position the useful order is to close what you already know about, then test to find what you did not.
There are also states where the result would not mean much once it arrived. An application in the middle of a refactor that is rewriting authorization will produce findings against code that will not exist by the time the report is read. The same is true of an estate being migrated: a thorough test of the environment you are leaving tells you about a system you are leaving. Waiting until the target is stable is not a delay, it is what makes the findings usable.
It is the right choice when a system is settled enough that findings will still apply when they are read, when there is genuine disagreement inside the team about whether a control works, or when a design assumption has never been put in front of someone actively trying to break it. Authorization logic is a clear case: it is easy to reason about on paper and hard to be sure of without someone deliberately attempting to cross a boundary while holding a valid account.
Production or a Copy: What the Test Runs Against
The first structural decision in scoping is which environment the work targets. Testing production gives an answer about the system your users actually reach, with the same WAF rules, the same rate limits, the same identity provider and the same data. It also carries the risk that proving an issue touches live records or degrades a service, which is why the agreed testing windows and the escalation path carry more weight here than anywhere else in the engagement.
A staging or preproduction copy removes that risk and introduces a different one: the copy is only useful to the degree it matches. The differences that cause trouble are rarely in the application code. They are the things nobody counts as part of the app, such as a permissive CORS policy that only exists in staging, seeded accounts holding roles no real user has, no WAF in front, a separate identity provider tenant, or storage permissions set for convenience. A finding against a copy can be a real production issue, an artifact of the copy, or a production issue the copy is masking, and separating those costs time that would otherwise go into testing.
If you test against a copy, the practical move is to hand over the places it is known to differ before testing starts rather than after a finding is written against one of them. Known differences can then be handled as configuration questions in their own right instead of being chased as though they were live behavior.
Credentialed Testing and What the Account Set Decides
Most of what is listed above under web applications and APIs, including authorization and access control, broken object-level authorization and business-logic flaws, is only reachable from inside a valid session. An unauthenticated test answers what is exposed before login, which is a real question but a narrow one. The failures that matter in those categories belong to someone who already holds a legitimate account and uses it to reach data owned by another user, another tenant or another privilege tier.
If accounts cannot be provisioned in the environment being tested, say so while scope is still open. The test can still run, but the report will not answer authorization questions, and that is better known going in than discovered in the findings. Where accounts can be provisioned, the set that makes authorization testing possible is:
- Two separate accounts in the same role, so access from one to the other's data can actually be attempted rather than reasoned about.
- One account at each distinct privilege tier the product really has, including tiers that only exist for internal staff.
- Two tenants wherever tenancy is a concept, since cross-tenant access cannot be tested from inside a single tenant.
- An account for any admin console, or a written decision that the console sits outside the scope.
What Blocks a Test Once It Is Running
Defenses that work will interfere with testing, and that is not a fault, it just has to be planned for. A WAF that drops a payload, rate limiting that throttles a request series, or an IP reputation service that blackholes the testing source can each make a vulnerable application look sound. The problem is ambiguity rather than inconvenience: the report cannot say whether the underlying flaw is absent or simply unreachable from where the test was sitting.
There are three ways to resolve that, and they answer different questions. Allowlist the testing source, and the result is about the application itself. Leave the controls in place, and the result is about the perimeter as deployed. Do both in sequence, and report them separately, which gives you the gap between the two. Pick one during scoping, because switching partway through spends window time on setup.
Two mechanical blockers are worth naming in advance. Lockout policies will disable a test account after failed attempts and can end a credentialed test partway through, and if the accounts are shared with real users, it takes them out too. Caching or a CDN in front of the application can also serve a stored response, which makes it look like the origin behaved a certain way when the origin was never asked, so cache behavior for the tested paths is something to establish before a finding is written on top of it.
Frequently Asked Questions
What is a penetration test here, and what is it not?
It is not a source code audit, not a compliance certificate, and not the remediation work itself. Testing runs against a system as it is deployed in an environment you provide, so it answers questions about runtime behavior rather than about every path through the codebase; reviewing the code itself is separate work and belongs with secure development lifecycle engagements. No finding asserts anything about systems outside the agreed scope, and what is covered inside the scope is bounded by the agreed testing windows.
What has to already exist before testing can start?
A reachable target environment, accounts provisioned at the privilege levels the scope calls for, a written inventory of the hostnames, IP ranges and API endpoints that are in scope, and a named contact who can be reached during the testing windows. Access needs to be verified as working rather than assumed, because an account that silently fails to authenticate is indistinguishable from an access control doing its job, and untangling that consumes window time on a question that is not about your security. The inventory matters for the same reason: anything not on it is outside the scope, so an endpoint nobody remembered will not be looked at.
We would be working with you remotely. How does an internal network test reach an internal network?
Through either a jump host you stand up inside the segment the test is meant to originate from, or VPN access issued to named tester accounts on your own remote access system. Both keep the credentials and the connection under your control, so access ends when you close it. Placement then decides what the test can tell you: a host on a flat management segment and a host on a user VLAN produce different answers about lateral movement and segmentation, so it belongs wherever the question you actually have lives. Outbound access from that host also has to be permitted, or the tooling will not run at all.
Once access is in place, how does the work proceed?
Access is provisioned and verified first, then the in-scope surface is enumerated against the inventory so both sides agree on what is being examined before anything is attempted against it. Testing runs inside the agreed windows, and anything that reaches live data or looks actively exploitable goes to your escalation contact while the window is open rather than sitting until delivery. The report follows, then a walkthrough with the engineers who own the affected code or configuration, since questions about a finding get settled in that conversation instead of in a mail thread.
How are findings written so a development team can work them?
Each finding is written as one issue with one owner rather than a bundle of related problems, so it can be assigned and tracked in whatever tracker you already run. Where several findings share a root cause, that is stated, so you are not paying to fix the same defect in five places without knowing it is one defect. The ranking will not always line up with a published severity score for the same class of bug: something rated critical in the abstract can be unreachable behind an authorization check you already have, and something rated moderate can be the step that made the rest of the chain work. If you triage by score alone, that difference is the thing to watch for.
What data gets touched, and what stays under our control?
Testing works through the accounts and environments you provision, so access is yours to grant and yours to revoke, and it stops when you close it. Where proving a finding would mean reading real customer records, we confirm with your escalation contact how much proof you want first, since reaching a record that should not have been reachable is usually demonstrable without pulling its contents. What is captured is kept as evidence supporting the report. If you have requirements about how that evidence is stored or how long it is held, raise them during scoping so they are part of the agreement rather than a question asked afterward.
What do we have to decide before the scope can be closed?
Three things that change the shape of the work: whether your own detection and response team is told a test is running, whether services you do not host yourself are included, and whether any technique is ruled out. If your defenders are not told, at least one person senior enough to call off a response still has to know, otherwise normal testing traffic gets handled as a live incident. Testing hosted or managed services generally requires permission from the provider as well as from you, which is worth starting early because it sits outside both our control and yours. Denial of service and anything else that would degrade a running service is excluded unless you specifically want resilience tested, and social engineering of your staff is a separate decision rather than an assumed part of an application test.