Blog

What an autonomous pentest looks like when the site is actually hardened

16 findings, zero critical, zero high, nothing exploited. The most useful report we published this week is the boring one, because it shows what the agent does when there is no way in.

· 6 min read

Every other campaign write-up we published this week ends in a compromise. This one does not, and it is the one we would show first to anyone evaluating an autonomous pentester. The target is asc-it.fr, our own company site, tested non-destructively with no special access.

The result: 16 findings, zero critical, zero high, zero exploited. Overall posture recorded as medium. What follows is what the agent does with a target that does not fall over.

What the agent found

SeverityCount
Critical0
High0
Medium4 (confirmed)
Low7 (6 confirmed, 1 unconfirmed)
Informational5 (confirmed)
Total16

What was already right

The report opens by stating what held, which matters as much as the list of gaps. The site is an Angular server side rendered application behind a CDN, with HSTS including subdomains and preload, TLS 1.3, and strong email authentication with a DMARC policy of reject. Nuclei identified no known CVEs. There are no authentication flows, no cookies and no file upload, so the exploitable surface is genuinely small, and the report says so rather than padding the list to look thorough.

The four medium findings

  • CORS origin reflection with credentials on the CDN. The most interesting finding in the report, and the only one that is a real cross origin issue.
  • A stack trace through the TRACE method.
  • No Content-Security-Policy header on the HTML responses.
  • Verbose backend errors leaking the internal GraphQL query structure from the CMS API.

The CORS behaviour was proven with one request:

curl -sI -H "Origin: https://evil.com" https://<cdn-host>/files/<object>

HTTP/2 200
access-control-allow-origin: https://evil.com
access-control-allow-methods: GET,HEAD,OPTIONS,POST
access-control-allow-credentials: true
access-control-allow-headers: *
vary: origin

The CDN reflects whatever origin is sent and sets Access-Control-Allow-Credentials: true next to it. In isolation that is the textbook misconfiguration. The severity stayed at medium because the assets served there are public and there is no authenticated session to ride, which the report states rather than leaving the reader to assume the worst.

The TRACE finding is a good example of a small thing that leaks a real detail:

curl -sD - -X TRACE https://asc-it.fr/accueil

HTTP/2 500
content-type: text/html; charset=utf-8

<pre>TypeError: 'TRACE' HTTP method is unsupported.
    at new Request (node:internal/deps/undici/undici:9927:21)
    at ... (file:///usr/app/server/server.mjs:66:1141)</pre>

An unsupported method produces an unhandled error, and the error page prints a filesystem path and the runtime internals. Nothing is exploitable from it. It is still information the server should not volunteer, and it is exactly the class of finding that only shows up when someone sends a method nobody sends.

The low and informational findings

  • The API gateway URL and the tenant identifier are hardcoded in the JavaScript bundle, which is normal for a single page application and worth knowing when you model the attack surface.
  • Missing Permissions-Policy, Referrer-Policy and Cache-Control headers.
  • A missing DKIM record, recorded as unconfirmed, because the agent cannot enumerate selectors it does not know.
  • A deprecated X-XSS-Protection header still in use, rate limit configuration visible in response headers, a directly reachable third party form endpoint, and CMS platform details disclosed by the CDN.

The finding we like most: the agent caught its own false positive source

One informational finding reads: the single page application catch-all returns HTTP 200 for all paths. The evidence endpoint is asc-it.fr/.env.

A request for /.env returning 200 is, on most scanners, a critical secrets exposure. The agent checked what the body actually was, found the application shell rather than an environment file, and filed the behaviour itself as informational, noting that it is misleading to scanners. That is the difference between a tool that counts status codes and one that reads responses. On a target with nothing to find, the ability to not invent something is the entire product.

Remediation

  • Replace origin reflection with an explicit allowlist on the CDN, and do not pair a reflected origin with credentials.
  • Disable the TRACE method at the edge, and return a generic error page instead of a stack trace.
  • Add a Content-Security-Policy, plus Referrer-Policy, Permissions-Policy and explicit Cache-Control on HTML responses, and drop the deprecated X-XSS-Protection header.
  • Return generic backend errors so that the internal query structure and storage paths stay internal.
  • Publish a DKIM record to complete the SPF and DMARC setup that is already in place.
  • Consider returning 404 for unknown paths rather than a 200 shell.

What this proves about autonomous pentesting

A tool that produces critical findings on every target is not a good tool, it is an unreliable one. The same platform that chained anonymous storage access to an administrator credential elsewhere returned zero critical and zero high here, and spent its run documenting header hygiene and one CORS header on a CDN.

We publish this report for the same reason we publish the ones where the agent demoted its own claim. The value of an autonomous pentester is not the size of the number it produces. It is whether you can act on the number without checking it first.

Darkmoon is open source under GPL-3.0. Point it at something you own and see what it says: repository, documentation.

Run it against your own lab

Darkmoon is open source (GPL-3.0) and self hosted. Clone it, point it at a target you own, and read every line.