I gave it one thing: a single in-scope IP. Everything after that, mapping the attack surface, fingerprinting the one exposed service, and matching it against a vulnerability that had been disclosed as an actively-exploited zero-day that same day, the agents did on their own. This is a walkthrough of that run. Client data is redacted throughout; the interesting part isn’t who the target was, it’s how little I had to drive.
The plugin
The tool is Claude-ExternalPentest — a Claude Code plugin that runs a methodology-driven external test through the same phases a human would, aligned to PTES and the OWASP WSTG. Five commands, each backed by a focused sub-agent with scoped tool access:
| Step | Command | What runs |
|---|---|---|
| Scope | /ext-scope <targets> | Records allow/deny lists, windows, rate limits, forbidden actions |
| Recon + enum | /ext-recon | ext-enumerator: OSINT → discovery → web/TLS/DNS → services → nuclei. Read-only |
| Attack plan | /ext-attack-paths | ext-attack-planner: correlate exposures → ranked footholds by CVSS. Plan only |
| Exploit | /ext-exploit | ext-exploit-operator: one approved step at a time, confirms before anything intrusive |
| Report | /ext-report | ext-reporter: severity-ranked findings + evidence + remediation |
Every phase reads ./ext-engagement/scope.md and refuses to act outside it. That constraint is the whole reason this is safe to point at a production gateway.
1 · Scope — the only thing I typed
The entire manual input for the engagement was one line naming the in-scope IP:

From that one line the agent wrote a full scope.md: a single-host allowlist (“exact IP only”), a default-deny denylist that explicitly rules out the VPN concentrator, CDN/WAF edge, other tenants, and any off-target reverse-DNS hostname, plus a forbidden-actions list covering DoS, disruptive exploitation, phishing, lockout-risk brute force, and touching shared upstream infra.
In scope (allowlist)
- <redacted IP> — the single authorized host, exact IP only # [!code highlight]
Out of scope (denylist) — must not be probed
- Anything other than <redacted IP>
- VPN concentrator, cloud provider consoles, CDN/WAF edge, other tenants
- Any off-target reverse-DNS hostname or vhost
- Default-deny: any target matching neither list is out of scope.
No CIDR expansion, no pivoting, no subdomain/vhost testing.
Forbidden actions
- No DoS / stress / availability-impacting testing
- No exploitation that could disrupt production
- No password spraying / brute force that risks lockout
- No destructive payloads
BTW, the scope message itself tripped a real-time cyber safeguard flag before proceeding. It didn’t block the work, the run continued, in-scope, exactly as written, but if you do this kind of thing you’ll see the flag, and it’s worth knowing it’s expected rather than a sign something’s wrong.
2 · Recon — read-only, 19 minutes, one port
/ext-recon handed off to the ext-enumerator agent. No approval needed — the entire phase is read-only by design. Nineteen minutes later it came back with a tight surface:

The whole attack surface was one port:
- 443/tcp only — full 65,535-port TCP sweep, every other port filtered, UDP top-50 silent
- Service: SonicWall Secure Mobile Access (SMA) 1000 SSL-VPN “WorkPlace” portal,
Server: SMA/12.5 - TLS: solid — TLS 1.2 only, strong AEAD/PFS ciphers, no legacy weakness, valid OV cert
- nuclei: 6,356 templates (dos/intrusive/fuzz excluded) → 0 matches
- Exposures: no admin/default/backup panels across ~25 probed paths; decent security headers
Scope discipline held here too, the TLS cert revealed an off-target hostname, which the agent recorded as data only and never resolved or probed, because it wasn’t on the allowlist.
The commands were standard, rate-limited nmap, all logged to ext-engagement/recon/ for the report appendix:
# host liveness — ICMP filtered, confirmed via TCP SYN/ACK
nmap -sn -PS22,80,443,3389,8080,8443 -PA80,443 -T3 <target>
# full TCP sweep
nmap -Pn -T3 -p- --min-rate 300 --max-retries 2 <target>
# → 443/tcp open https (65,534 filtered)
# service / version detection
nmap -Pn -T3 -sV -sC --version-all -p 443 <target>
# → 443/tcp open ssl/https SMA/12.5
The single most important line in the whole recon phase is the one the agent flagged as unconfirmed: the banner exposes 12.5, but not the exact 12.5.0-NNNNN patch level. It refused to assume vulnerability from the banner alone.
3 · Attack planning — correlating a one-port surface against a same-day advisory
/ext-attack-paths launched the ext-attack-planner to correlate that confirmed SMA 1000 / WorkPlace 12.5 surface against known vulnerabilities — precise about SMA 1000 vs SMA 100 (different product lines), ranking footholds by prerequisites, intrusiveness, and appliance-crash risk. Plan only, no exploitation.

Here’s where the surface and the calendar collided. On the same day the target was being assessed, SonicWall published SNWLID-2026-0016: two SMA 1000 zero-days, confirmed actively exploited in the wild, in the exact component sitting on this host’s 443:
- CVE-2026-83548 — pre-authentication SSRF in the WorkPlace interface, CVSS 10.0, actively exploited
- CVE-2026-83549 — post-authentication OS command injection in the Appliance Management Console, CVSS 7.8
Chained, they move an attacker from unauthenticated external access to arbitrary OS command execution. The planner pulled the ranked footholds together and separated what applied from what didn’t:

The discipline in that table is the part I care about. It didn’t just grab every SonicWall CVE it could find. It excluded CVE-2025-23006 as not applicable (that one hits ≤12.4.3; the target is 12.5), it noted credential spraying was forbidden by scope regardless of whether it might work, and it pinned the entire question to one unknown:
The banner shows only
SMA/12.5— not the exact12.5.0-NNNNNbuild. That build is the single fact that decides vulnerable vs patched. Vulnerable at ≤12.5.0-02835, fixed at ≥12.5.0-02952.
So the recommended first move was not an exploit. It was a safe, non-intrusive build confirmation, read-only version/metadata endpoints, asset hashes, error-page build strings, to resolve vulnerable-vs-patched without touching auth, without lockout risk, without availability impact. Anything intrusive after that would stop and wait for approval.
4 · The finding, and where it stops
This is the responsible shape of the finding, and it’s a stronger result than a bare “exploited it”:
- The target exposes an internet-facing SMA 1000 WorkPlace portal on the 12.5 line.
- That line has a CVSS 10.0, actively-exploited, pre-auth vulnerability in the exact exposed component.
- Whether this host is exploitable comes down to one build number the banner hides.
- The next move is a read-only build check and regardless of the outcome, there’s an independent duty to report: an unpatched, internet-facing SMA 1000 on this appliance line may already be compromised. That’s a high-value finding on its own.
Two things still gated any intrusive step, and the agent named both rather than pushing past them: a confirmed testing window + rollback plan (not yet in scope.md, and required before touching a production VPN gateway), and client notification given active in-the-wild exploitation. No exploitation approval, no intrusive step. It asked first.
5 · The report
/ext-report turned the engagement artifacts into a client-ready document, executive summary, the single-service finding, and a full command appendix with raw output so everything is reproducible:


Why this is the interesting part
I want to be clear about what did and didn’t happen. I didn’t feed it a CVE to look for. I fed it an IP. The agents fingerprinted the surface down to a specific product and version line, then went and found which freshly-disclosed advisories were relevant to that surface, including one published the same day. The attack surface drove the vuln research, not the other way around. That’s the difference between a scanner running yesterday’s template set and an agent that reasons about what a SMA/12.5 banner means the day a matching 10.0 drops.
And it held the line in three places that matter on a real engagement: it stayed on the single in-scope IP, it never assumed exploitability from a banner, and it stopped at the boundary between read-only detection and intrusive proof-of-concept, surfacing the decision to me instead of making it.
The plugin is on GitHub — xcoy0te/Claude-ExternalPentest. It’s Markdown and a small manifest, so it’s easy to read exactly what each agent is and isn’t allowed to do before you point it at anything.