JS Recon: Reverse-Engineering SPA Attack Surfaces Through Framework-Aware Static Analysis
Hook
Modern SPAs hide up to 70% of their routes in lazy-loaded chunks that never appear in initial HTML. JS Recon treats framework bundler output as a reverse-engineering target, exploiting Next.js _buildManifest and Nuxt __NUXT__ objects to reconstruct attack surfaces that generic crawlers can't see.
Context
Traditional web reconnaissance assumes servers control routing—you crawl HTML links, discover endpoints, map the application. But single-page applications inverted this model. A Next.js dashboard might serve a 200KB initial bundle that references hundreds of code-split chunks, each loaded conditionally based on user role, feature flags, or navigation state. The full route tree exists client-side as build artifacts, but tools like LinkFinder and Katana treat these as opaque JavaScript—they'll extract hardcoded strings but miss the systematic chunk references that frameworks use internally.
This gap matters for security assessments. A fintech SPA might hide /admin/api-keys behind role-based routing that only loads for privileged users, but the chunk containing that component ships to every browser with a predictable filename in _next/static/chunks/. Attackers who enumerate these framework-specific patterns access the full client-side codebase without authentication. JS Recon emerged as purpose-built tooling for this reconnaissance phase, combining Chromium instrumentation with framework-specific parsers to treat modern bundler output as structured data rather than text to grep.
Technical Insight
JS Recon's architecture chains four distinct phases—lazyload, map, analyze, and report—but the framework-specific loaders in the lazyload phase are where it differentiates from generic crawlers. Instead of waiting for user interaction to trigger dynamic imports, it directly parses framework build artifacts. For Next.js, this means fetching _buildManifest.js and extracting chunk mappings:
// Simplified Next.js loader logic
const manifest = await page.evaluate(() => {
return window.__BUILD_MANIFEST;
});
// Manifest structure: { pages: { '/dashboard': ['chunks/123.js'], ... } }
for (const [route, chunks] of Object.entries(manifest.pages)) {
for (const chunk of chunks) {
const url = `${baseUrl}/_next/static/chunks/${chunk}`;
await fetchAndCache(url);
}
}
This isn't guessing—Next.js exposes this global for its own client-side router. JS Recon exploits the framework's internal conventions as a discovery mechanism. Similarly, for Nuxt applications, it targets the __NUXT__ payload object that contains preloaded route data and asyncData responses, often including API endpoints and GraphQL queries that never appear in rendered HTML.
The map phase uses Babel to parse discovered files into ASTs and build call graphs. This enables CS-MAST structural hashing—a technique borrowed from malware analysis that hashes normalized syntax trees to detect code reuse. When analyzing a typical React app, you might discover the same lodash bundle included in twelve different chunks due to webpack chunking strategies. CS-MAST deduplication reduces redundant analysis:
// Each AST node generates a structural hash
function hashNode(node: Node): string {
const normalized = {
type: node.type,
// Ignore variable names, keep structure
children: node.children?.map(hashNode)
};
return crypto.createHash('sha256')
.update(JSON.stringify(normalized))
.digest('hex');
}
The analyze module performs pattern matching for security issues, but here's where the architecture shows limitations. Despite claiming "dataflow analysis," inspection reveals regex-based detection:
// Typical pattern from analyze module
const patterns = {
hardcodedSecrets: /(?:api[_-]?key|secret|token)\s*[=:]\s*['"][^'"]{20,}/gi,
dangerousEval: /eval\s*\(/g,
openRedirect: /(?:window\.location|location\.href)\s*=\s*[^;]+/g
};
This catches low-hanging fruit but misses context-dependent vulnerabilities. A window.location = userInput assignment is only exploitable if userInput flows from untrusted sources, but these patterns fire on any assignment. True dataflow analysis would require symbolic execution through async boundaries and React component trees—computationally expensive and outside this tool's scope.
The most forward-looking feature is MCP (Model Context Protocol) server integration. This exposes discovered JavaScript as a queryable corpus for LLMs:
// MCP server exposes tools LLMs can invoke
server.tool('searchCode', async ({ pattern, fileTypes }) => {
const results = await codeSearch(pattern, fileTypes);
return results.map(r => ({ file: r.path, snippet: r.code }));
});
server.tool('analyzeFunction', async ({ functionName }) => {
const ast = getASTForFunction(functionName);
return { ast, callGraph: buildCallGraph(ast) };
});
This positions JS Recon as infrastructure for agentic security workflows. An LLM agent could query "find all API endpoints that accept file uploads," receive code snippets, generate exploitation theories, and iterate—treating the entire discovered codebase as conversation context. This is speculative tooling for a future where LLMs drive reconnaissance, but the architecture is already in place.
The proxy layer deserves mention for production use. JS Recon integrates Oxylabs residential proxies with CDN/WAF fingerprinting—it detects Cloudflare challenge pages and Akamai bot detection, only rotating to paid proxies when confidence thresholds are met. This quota-aware approach prevents burning expensive residential IPs on targets that don't require them. The implementation tracks origin-level success rates and fails back to direct connections for unprotected targets, a pragmatic cost optimization missing from tools that blanket-proxy everything.
Gotcha
Framework coverage has conspicuous gaps. Angular support only works for v17+ with esbuild; the ubiquitous webpack-based Angular applications (versions 2-16) are unsupported. Ember, Backbone, and server-rendered frameworks like Astro SSR aren't handled—the README claims Astro support but only parses client bundles, missing server endpoints entirely. If your target uses pre-2023 framework versions or hybrid SSR/CSR architectures, JS Recon will miss significant portions of the application.
The analyze module's pattern-based detection creates false positive noise. Every eval() call triggers alerts regardless of context—legitimate uses in sandboxed template engines or safe JSON parsing get flagged alongside actual vulnerabilities. The tool lacks suppression mechanisms or confidence scoring, so you'll spend time triaging noise. For serious SAST, export discovered files and run Semgrep with proper taint tracking rules. JS Recon is recon infrastructure, not a replacement for dedicated static analysis. The Oxylabs dependency is also concerning—there's hard-coded vendor lock-in with no abstraction for Bright Data, Smartproxy, or other residential proxy providers. If Oxylabs pricing changes or service degrades, you're rewriting integration code rather than swapping configuration.
Verdict
Use if: You're assessing modern SPAs (Next.js, Nuxt, Vue) where routes and API clients hide in lazy-loaded chunks, you need to map client-side attack surface without credentials, or you're building LLM-powered security tooling that needs structured access to discovered JavaScript. The framework-specific loaders and MCP integration are genuinely novel for 2024 reconnaissance workflows. Skip if: Your targets use pre-2023 frameworks (Angular <17, Ember), heavily obfuscated code (run deobfuscation first), or you already have comprehensive proxy history from authenticated sessions (export to Burp/Caido for analysis instead). Also skip if you need production-grade SAST—the pattern matching here is surface-level triage, not vulnerability confirmation. This is a recon multiplier for modern SPAs, not a general-purpose JavaScript security scanner.