Strobesstrobes
Platform
Solutions
Resources
Customers
Company
Pricing
Book a Demo
Strobesstrobes

Strobes connects every exposure signal to autonomous action, so security teams fix what matters, prove what works, and stop chasing noise.

Book a DemoTalk to an expert
ISO 27001SOC 2CREST
  • Platform
  • Platform Overview
  • Agentic Exposure Management
  • AI Agents
  • Integrations
  • API & Developers
  • Workflows & Automation
  • Analytics & Reporting
  • Solutions
  • Exposure Assessment (EAP)
  • Attack Surface Management
  • Application Security Posture
  • Risk-Based Vulnerability Management
  • Adversarial Exposure Validation (AEV)
  • AI Pentesting
  • Pentesting as a Service
  • CTEM Framework
  • By Industry
  • Financial Institutions
  • Technology
  • Retail
  • Healthcare
  • Manufacturing
  • By Roles
  • CISOs
  • Security Directors
  • Cloud Security Leaders
  • App Sec Leaders
  • Resources
  • Blog
  • Customer Stories
  • eBooks
  • Datasheets
  • Videos & Demos
  • Exposure Management Academy
  • CTEM Maturity Assessment
  • Pentest Health Check
  • Security Tool ROI Calculator
  • Company
  • About Strobes
  • Meet the Team
  • Trust & Security
  • Contact Us
  • Careers
  • Become a Partner
  • Technology Partner
  • Partner Deal Registration
  • Press Release

Weekly insight for security leaders

CTEM research, agentic AI trends, and what's actually moving the needle.

© 2026 Strobes Security Inc. All rights reserved.

Privacy PolicyTerms of ServiceCookie PolicyAccessibilitySitemap
Back to Blog
How to pentest single-page applications - React, Angular and Vue SPA security testing guide
Penetration TestingApplication Security

How to Pentest Single-Page Applications (React, Angular, Vue)

AlibhaJune 4, 202623 min read

Table of Contents

  • Why Do SPAs Break Traditional Pentesting?
  • What Makes SPA Architecture Different from a Security Perspective?
  • Which Vulnerabilities Are Unique to Single-Page Applications?
    • DOM-Based XSS
    • Insecure Client-Side Storage
    • Exposed Secrets in JavaScript Bundles
    • Broken Client-Side Access Controls
    • Sensitive Data in Browser History and State
  • Why Do DAST Scanners Fail Against SPAs?
  • How Do You Set Up Your Testing Environment for SPA Pentesting?
    • Tools You Need
    • The Workflow
  • How Do You Test Client-Side Routing for Authorization Bypass?
    • Step-by-Step
  • How Do You Find Hardcoded Secrets in JavaScript Bundles?
    • Manual Approach
    • Automated Approach
  • How Do You Test DOM-Based XSS in SPAs?
    • Sources to Test
    • Sinks to Look For
    • Testing Process
  • What About JWT Handling, postMessage, and Prototype Pollution?
    • JWT Handling in the Browser
    • postMessage Vulnerabilities
    • Prototype Pollution
  • How Does AI Pentesting Solve the SPA Coverage Problem?
    • How Browser Bridge Works with SPAs
    • Agent Shell for API-Layer Testing
    • Supervisor Mode: Control During Testing
    • Scheduled Recurring Tests
  • SPA Pentesting Checklist
  • Frequently Asked Questions
    • Can OWASP ZAP test single-page applications effectively?
    • Do I need source code access to pentest a SPA?
    • Is localStorage always insecure for token storage?
    • How do you handle authentication during an automated SPA pentest?
    • What is the biggest mistake teams make when pentesting SPAs?
    • How often should you pentest a SPA?
  • Sources
  • Related Reading

Authors

A
Alibha

Share

Table of Contents

  • Why Do SPAs Break Traditional Pentesting?
  • What Makes SPA Architecture Different from a Security Perspective?
  • Which Vulnerabilities Are Unique to Single-Page Applications?
    • DOM-Based XSS
    • Insecure Client-Side Storage
    • Exposed Secrets in JavaScript Bundles
    • Broken Client-Side Access Controls
    • Sensitive Data in Browser History and State
  • Why Do DAST Scanners Fail Against SPAs?
  • How Do You Set Up Your Testing Environment for SPA Pentesting?
    • Tools You Need
    • The Workflow
  • How Do You Test Client-Side Routing for Authorization Bypass?
    • Step-by-Step
  • How Do You Find Hardcoded Secrets in JavaScript Bundles?
    • Manual Approach
    • Automated Approach
  • How Do You Test DOM-Based XSS in SPAs?
    • Sources to Test
    • Sinks to Look For
    • Testing Process
  • What About JWT Handling, postMessage, and Prototype Pollution?
    • JWT Handling in the Browser
    • postMessage Vulnerabilities
    • Prototype Pollution
  • How Does AI Pentesting Solve the SPA Coverage Problem?
    • How Browser Bridge Works with SPAs
    • Agent Shell for API-Layer Testing
    • Supervisor Mode: Control During Testing
    • Scheduled Recurring Tests
  • SPA Pentesting Checklist
  • Frequently Asked Questions
    • Can OWASP ZAP test single-page applications effectively?
    • Do I need source code access to pentest a SPA?
    • Is localStorage always insecure for token storage?
    • How do you handle authentication during an automated SPA pentest?
    • What is the biggest mistake teams make when pentesting SPAs?
    • How often should you pentest a SPA?
  • Sources
  • Related Reading

Authors

A
Alibha

Share

TL;DR
  • Single-page applications render content client-side, use JavaScript routing, and communicate through async API calls, which breaks traditional DAST crawlers that expect server-rendered HTML.
  • SPA-specific vulnerabilities include DOM-based XSS, tokens stored in localStorage, exposed API keys in JavaScript bundles, broken client-side access controls, and postMessage flaws.
  • Traditional scanners like OWASP ZAP and Burp’s default crawler miss 40-60% of SPA routes because they can’t execute JavaScript or interact with dynamic UI state the way a real browser does.
  • You need a proxy-first approach (Burp Suite + browser extension), bundle analysis for hardcoded secrets, and manual testing of client-side routing for authorization bypass.
  • Strobes AI agents use Browser Bridge to interact with SPAs like a real user, clicking through UI flows, navigating client-side routes, and filling forms to map the full attack surface automatically.

Why Do SPAs Break Traditional Pentesting?

SPAs break traditional pentesting because the server doesn’t render pages. The browser does. A React, Angular, or Vue application ships a JavaScript bundle to the client, and that bundle builds every view, handles every route transition, and manages every API call at runtime. For a security scanner that expects to crawl <a href="/page"> links and parse server-rendered HTML, the SPA looks like a single blank page with a <div id="root"></div>.

This isn’t a minor inconvenience. It’s a fundamental mismatch between how most DAST tools work and how modern web apps are built. If your pentest methodology was designed around server-side rendering, you’re testing 2010-era architecture against 2026-era applications. The result? Missed routes, missed API endpoints, missed vulnerabilities.

According to the 2024 State of JS survey, React alone powers over 50% of production web applications. Angular and Vue account for a significant share of the rest. If your pentesting toolkit can’t handle client-side rendering, you’re blind to the majority of web applications deployed today.

What Makes SPA Architecture Different from a Security Perspective?

SPAs shift security-critical logic from the server to the browser, which changes the threat model entirely. In a traditional multi-page application, the server controls routing, session management, and page rendering. In a SPA, the client handles all three.

Here’s what changes:

Routing lives in JavaScript. When a user visits /dashboard/admin/settings, no HTTP request goes to the server for that specific path. The JavaScript router (React Router, Angular Router, Vue Router) parses the URL fragment or path and renders the corresponding component. The server never sees individual route requests, which means server-side access controls on those routes don’t exist unless the developer explicitly built API-level authorization.

State lives in the browser. SPAs use state management libraries (Redux, NgRx, Vuex/Pinia) to store application data on the client. That state often includes user roles, permissions, feature flags, and even tokens. All of it is inspectable through browser DevTools.

API communication is decoupled. Instead of form submissions and page reloads, SPAs fire async HTTP requests (fetch/XHR) to REST or GraphQL endpoints. The API is a separate attack surface from the UI, and testing one without the other leaves gaps.

The DOM changes without page loads. New content appears through JavaScript DOM manipulation, not server responses. A scanner that triggers on DOMContentLoaded and then reads the HTML will miss every view that requires user interaction to render.

Traditional Web App Single-Page Application
Server renders each pageBrowser renders all views from JS bundle
Routes map to server endpointsRoutes handled by JS router (no server request)
Session cookie sent per requestJWT or token in localStorage/sessionStorage
Forms submit via POSTAPI calls via fetch/XHR (JSON payloads)
Crawlable <a href> linksDynamic navigation via history.pushState()

Which Vulnerabilities Are Unique to Single-Page Applications?

SPAs introduce a category of vulnerabilities that either don’t exist in traditional web apps or behave differently enough to require new testing techniques. Here are the ones that matter most.

DOM-Based XSS

DOM-based XSS happens when user input flows through JavaScript DOM manipulation without sanitization. Unlike reflected or stored XSS, the payload never touches the server. It executes entirely in the browser.

SPAs are especially prone to this because they use innerHTML, dangerouslySetInnerHTML (React), or [innerHTML] binding (Angular) to render dynamic content. If a URL parameter, hash fragment, or postMessage payload reaches one of these sinks, you’ve got XSS.

Common sources in SPAs: location.hash, location.search, document.referrer, window.name, postMessage data, and URL parameters parsed by the client-side router.

Insecure Client-Side Storage

SPAs frequently store authentication tokens in localStorage or sessionStorage instead of httpOnly cookies. This is dangerous because any XSS vulnerability on the page can read localStorage with a single line of JavaScript: localStorage.getItem('token'). An httpOnly cookie, by contrast, is invisible to JavaScript.

You’ll also find API keys, user profile data, feature flags, and sometimes full API responses cached in client-side storage. Check localStorage, sessionStorage, IndexedDB, and even window.__INITIAL_STATE__ (used by server-side rendering hydration in frameworks like Next.js and Nuxt).

Exposed Secrets in JavaScript Bundles

Webpack, Vite, Rollup, and other bundlers compile all application code (and sometimes configuration) into JavaScript files served to the browser. Developers who reference API keys, internal endpoints, or secret tokens in their source code often don’t realize those values end up in the production bundle, readable by anyone.

Common findings: AWS access keys, Stripe publishable/secret key pairs, internal microservice URLs, feature flag configurations, admin API endpoints not documented anywhere, and hardcoded credentials for staging environments.

Broken Client-Side Access Controls

SPAs often implement access controls purely in the frontend. A React component might check user.role === 'admin' before rendering an admin panel. But if the underlying API endpoint doesn’t enforce the same check, any user who calls the API directly (or just edits the JavaScript in DevTools) can access admin functionality.

This is one of the most common SPA findings: the UI hides features based on roles, but the API serves data to anyone with a valid token.

Sensitive Data in Browser History and State

SPAs that use history.pushState() can leak sensitive data through browser history entries. If a search query, patient ID, or financial record identifier appears in the URL path or query string, it ends up in the browser history, potentially accessible to anyone with physical access to the device or through a shared browser profile. The history.state object itself can also carry data between route transitions.

Why Do DAST Scanners Fail Against SPAs?

Most DAST tools were built for server-rendered applications, and they fail against SPAs in predictable ways. OWASP ZAP’s default spider follows <a href> links and form actions in the HTML response. Burp Scanner’s crawler does the same. Neither executes JavaScript by default in their crawling phase.

Here’s what that means in practice:

They miss client-side routes. A React app with 50 routes in its router configuration looks like one page to a non-JS crawler. ZAP’s spider returns a single URL. The other 49 routes, along with all their associated API calls and input fields, are invisible.

They can’t interact with UI components. A dropdown menu that loads options via API call, a modal that appears on button click, an infinite-scroll list, a multi-step form wizard: none of these render without JavaScript execution and user interaction. A crawler that doesn’t click buttons can’t reach them.

They miss authenticated states. SPAs often manage authentication entirely in JavaScript. The login flow stores a JWT in memory or localStorage, and every subsequent API call includes it as a Bearer token. A traditional crawler that relies on cookie-based session management can’t maintain an authenticated SPA session.

They can’t handle dynamic DOM changes. SPAs constantly modify the DOM in response to user actions and API responses. A scanner that takes one snapshot of the DOM after page load misses every state change. That’s where the vulnerabilities hide.

Scanner Limitation Impact on SPA Testing
No JavaScript executionSees only the initial HTML shell
Link-based crawling onlyMisses all JS router-defined routes
No UI interaction capabilityCan’t trigger modals, dropdowns, form wizards
Cookie-based session handlingCan’t maintain JWT-based auth
Single DOM snapshotMisses dynamically rendered content

ZAP’s AJAX Spider (which uses a headless browser) helps somewhat, but it still relies on heuristic clicking rather than understanding the application’s actual navigation flow. It’ll click random elements and hope to discover routes, which gives incomplete coverage and produces noisy results.

Your Scanner Is Missing 40-60% of Your SPA’s Routes. Start Testing What It Can’t See.

You just read why traditional DAST tools fail against SPAs — no JavaScript execution, no UI interaction, no client-side route discovery. Start a free trial to test your React, Angular, or Vue application with an AI agent that navigates your SPA the way a real user does.

First pentest running in under an hour. No procurement needed.

Start Your Free Trial →

How Do You Set Up Your Testing Environment for SPA Pentesting?

Start with a proxy-first setup. Route all browser traffic through Burp Suite or OWASP ZAP, then manually browse the application while the proxy records every request. This is the most reliable way to map a SPA’s API surface.

Tools You Need

Burp Suite Professional with the embedded browser. Burp’s embedded Chromium browser sends all traffic through the proxy automatically. This is your primary testing tool. Use the Proxy > HTTP History tab to capture every API call the SPA makes as you navigate.

Chrome DevTools. Open the Network tab to watch API calls in real time. The Sources tab lets you inspect JavaScript bundles. The Application tab shows localStorage, sessionStorage, cookies, IndexedDB, and service workers. The Console tab is where you’ll test for DOM-based XSS sinks.

Browser extensions for Burp. If you’re using your regular browser instead of Burp’s embedded one, install FoxyProxy or the Burp Suite browser extension to route traffic through the proxy.

webpack-bundle-analyzer or source-map-explorer. If the application ships source maps (many staging and some production deployments do), these tools visualize the entire bundle contents, making it trivial to find hardcoded secrets, internal API paths, and third-party library versions.

Retire.js. Scans JavaScript libraries for known vulnerabilities. Run it against the SPA’s loaded scripts to find outdated jQuery, Lodash, Angular, or other libraries with published CVEs.

Semgrep with JavaScript/TypeScript rulesets. Static analysis on the SPA’s client-side code (if you have source access) catches DOM XSS patterns, insecure postMessage handlers, and unsafe eval() usage before you even start dynamic testing.

The Workflow

  1. Configure your browser to proxy through Burp Suite on 127.0.0.1:8080.
  2. Install the Burp CA certificate in the browser so HTTPS interception works.
  3. Navigate to the target SPA and log in.
  4. Manually walk through every feature: click every button, open every menu, submit every form, trigger every error state.
  5. Watch Burp’s HTTP History fill up with API calls. Each call represents an endpoint you need to test.
  6. Use the Target > Site Map to build a tree of all discovered API endpoints.

This manual browsing phase is critical because it’s the only reliable way to map a SPA’s full attack surface. Automated crawlers can supplement it, but they can’t replace it.

How Do You Test Client-Side Routing for Authorization Bypass?

Test by accessing routes directly that should be restricted to higher-privilege roles, and verify the API enforces the same restrictions. In most SPAs, the router checks a local variable (like isAdmin in the Redux store) before rendering a view. Bypassing that check is trivial.

Step-by-Step

  1. Log in as a low-privilege user and note which routes are visible in the navigation.
  2. Identify hidden routes by examining the JavaScript bundle. Search for route definitions in the source:
    • React Router: look for <Route path="/admin" or createBrowserRouter configurations
    • Angular: search for RouterModule.forRoot or loadChildren in lazy-loaded modules
    • Vue Router: find the routes array in the router config
  3. Navigate directly to restricted routes by typing the URL in the browser. Example: if /admin/users isn’t shown in the nav for your low-privilege user, try visiting it directly.
  4. Check what happens. Three outcomes:
    • The page renders and the API returns data (full authorization bypass, critical finding)
    • The page renders but the API returns 403 (UI-only control, medium finding since it leaks route structure)
    • The router redirects you to /login or /unauthorized (properly implemented client-side guard, but still test the API directly)
  5. Test the API directly using Burp Repeater. Take a captured admin API request, swap in your low-privilege user’s token, and replay it. This is where you find the real authorization bugs.

The pattern here is consistent: never trust client-side routing to enforce access control. Always verify at the API layer.

How Do You Find Hardcoded Secrets in JavaScript Bundles?

Download the SPA’s JavaScript bundles and search them for secrets, internal URLs, and configuration values that shouldn’t be exposed to end users. Every SPA ships its compiled JavaScript to the browser, and developers regularly embed sensitive values during the build process.

Manual Approach

  1. Open Chrome DevTools > Sources tab.
  2. Find the main JavaScript bundle(s), usually named something like main.abc123.js or app.bundle.js in the static/js/ or assets/ directory.
  3. Click the {} (Pretty Print) button to format the minified code.
  4. Search (Ctrl+F) for these patterns:
    • apiKey, api_key, API_KEY
    • secret, password, token, credential
    • AWS_ACCESS_KEY, STRIPE_, FIREBASE_
    • Internal domain names: internal., staging., dev., .local
    • Bearer, Basic (hardcoded auth headers)
    • -----BEGIN (private keys, yes, people actually bundle these)

Automated Approach

If source maps are available (check for .map files alongside the JavaScript bundles or look for //# sourceMappingURL= comments), use source-map-explorer or webpack-bundle-analyzer to reconstruct the original source tree.

For source-level access, run Semgrep with its p/secrets ruleset:

semgrep --config p/secrets ./src/

This catches API keys, private keys, and credentials that made it into the codebase. Combine it with trufflehog for git history scanning to find secrets that were committed and then “removed” (they’re still in the git log).

Also check environment variable usage. React apps expose any variable prefixed with REACT_APP_, Vue CLI exposes VUE_APP_, and Angular makes environment.ts available in the bundle. If a developer puts a secret in any of these, it ships to every user’s browser.

Found Secrets in Your JS Bundles? Get a Full SPA Security Assessment.

Hardcoded API keys and exposed internal endpoints are just the surface. A comprehensive SPA pentest covers client-side routing bypass, DOM XSS, JWT mishandling, and the API layer underneath. Request a quote for a thorough assessment of your single-page application.

Custom scoping for React, Angular, and Vue applications.

Request a Pentest Quote →

How Do You Test DOM-Based XSS in SPAs?

Trace user-controllable inputs from their source (URL parameters, hash fragments, postMessage) to dangerous sinks (innerHTML, eval, document.write) in the JavaScript code. DOM XSS in SPAs is harder to find with automated scanners because the payload never appears in an HTTP response.

Sources to Test

  • URL parameters: ?search=<payload> parsed by the SPA’s router or custom query-string parser
  • Hash fragments: #section=<payload> commonly used in Angular’s HashLocationStrategy
  • postMessage: messages from other windows or iframes
  • localStorage/sessionStorage: data written by one feature and read by another
  • WebSocket messages: if the SPA uses real-time communication

Sinks to Look For

In React: dangerouslySetInnerHTML, direct DOM manipulation via refs, and eval() calls. React’s JSX escapes content by default, so XSS usually requires one of these explicit bypasses.

In Angular: [innerHTML] binding, bypassSecurityTrustHtml(), and the DomSanitizer service being misused to whitelist unsanitized content.

In Vue: v-html directive, which renders raw HTML without sanitization.

Testing Process

  1. Identify all URL parameters the SPA reads. Open the network tab, browse the app, and note which query parameters or hash values the JavaScript parses.
  2. Inject test payloads into each parameter. Start with <img src=x onerror=alert(1)> and framework-specific payloads.
  3. Monitor the DOM (use Chrome DevTools Elements panel with “Break on subtree modifications”) to see if your payload reaches an unsafe sink.
  4. For postMessage testing, open the browser console and run:
window.addEventListener('message', function(e) {
  console.log('postMessage received:', e.origin, e.data);
});

Then interact with the application to see what messages are being sent between frames.

What About JWT Handling, postMessage, and Prototype Pollution?

These three vulnerability classes show up repeatedly in SPA pentests, and each requires specific testing approaches.

JWT Handling in the Browser

SPAs that use JWTs for authentication face a storage dilemma. Storing the token in localStorage makes it accessible to XSS. Storing it in a cookie requires CSRF protections. Storing it in JavaScript memory means it’s lost on page refresh.

Test for: JWTs stored in localStorage (grab them with DevTools and check if they can be used from another browser), missing token expiration (decode the JWT at jwt.io and check the exp claim), algorithm confusion attacks (change alg to none or HS256 when the server expects RS256), and tokens that contain excessive user data (PII in the payload that any JavaScript on the page can read). See Auth0’s token storage guidance for the defensive side.

postMessage Vulnerabilities

SPAs that embed iframes or communicate with popups (OAuth flows, payment widgets, chat integrations) use window.postMessage(). The vulnerability occurs when the receiving window doesn’t validate the message origin.

Test for: missing event.origin checks in message handlers, sensitive data sent via postMessage without origin restrictions, and message handlers that execute code or modify the DOM based on untrusted message content.

Prototype Pollution

Prototype pollution is a JavaScript-specific attack where an attacker modifies Object.prototype, causing every object in the application to inherit the polluted properties. In SPAs, this can lead to XSS, privilege escalation, or denial of service.

Common entry points: URL query parameters parsed with vulnerable libraries (qs, lodash’s merge, jQuery’s $.extend), JSON input that’s deeply merged without sanitization, and state management actions that merge user input into the application store.

Test by injecting __proto__[polluted]=true or constructor[prototype][polluted]=true into merge-able inputs and checking if ({}).polluted returns true in the console.

How Does AI Pentesting Solve the SPA Coverage Problem?

AI pentesting solves the SPA coverage problem by doing what traditional scanners can’t: interacting with the application through a real browser, the same way a human tester would. Strobes AI agents use Browser Bridge to drive an actual browser instance, clicking buttons, filling forms, navigating menus, and waiting for async content to load.

This matters because a SPA’s attack surface is only visible through interaction. You can’t discover that a dropdown menu loads a list of user accounts via API call unless you actually click the dropdown. You can’t find that a multi-step form wizard has an IDOR on step 3 unless you complete steps 1 and 2 first. Traditional crawlers don’t do this. The Strobes AI agent does.

How Browser Bridge Works with SPAs

The Browser Bridge runs a real browser (Chrome) either in the cloud or on your local machine via the Local Browser Bridge. The AI agent controls this browser the way a human tester would: it reads the page, identifies interactive elements, clicks them, types into input fields, and observes the results. For SPAs, this means:

  • Full route discovery. The agent navigates through the application’s UI, triggering every client-side route transition. It doesn’t rely on parsing <a> tags from HTML; it interacts with the router by clicking navigation elements.
  • Dynamic content mapping. The agent waits for async API calls to complete, observes DOM changes, and maps every API endpoint the SPA contacts.
  • Authenticated testing. The agent logs in through the actual login form (credentials stored securely in the Credentials Vault), maintains the session token, and tests every feature the logged-in user can access.
  • Stateful interaction. Multi-step flows (checkout processes, form wizards, configuration screens) are tested end-to-end because the agent maintains state across steps.

Agent Shell for API-Layer Testing

While Browser Bridge handles the client-side, Agent Shell gives the AI agent terminal access to run tools like sqlmap, nuclei, and custom scripts against the backend API endpoints discovered during the browser-based crawl. The combination of browser-based UI testing and shell-based API testing covers both attack surfaces. Learn more about automated pentesting with Strobes.

Supervisor Mode: Control During Testing

Supervisor Mode lets you choose how much autonomy the AI agent has. For a first-time SPA pentest, use User mode: the agent pauses before each major action and waits for your approval. You can see exactly what it’s testing, modify its approach if needed, or reject actions that aren’t appropriate for your environment. Once you’re comfortable with the agent’s behavior, switch to Auto mode for fully autonomous runs.

For production SPAs, start with User mode. Approve each action for the first 10-15 steps to validate the agent’s approach, then switch to Auto.

See How Browser Bridge Discovers Every Route Your Scanner Misses

You just read how the Strobes AI agent uses Browser Bridge to click through SPAs, map client-side routes, and test authenticated flows — everything traditional crawlers fail at. Watch it test a real SPA end-to-end.

Full SPA testing walkthrough — no signup required.

See AI Pentesting in Action →

Scheduled Recurring Tests

SPAs change frequently. Every sprint ships new components, new routes, and new API endpoints. A one-time pentest goes stale within weeks. Strobes Schedules let you run the same pentest weekly or monthly, with automatic diff reports showing what’s new, what’s fixed, and what’s still open. For a SPA that deploys weekly, a weekly scheduled pentest ensures new code gets tested before the next deployment cycle buries it.

SPA Pentesting Checklist

Use this as a quick reference during your next SPA engagement.

Test Area What to Check Tools
Client-side routingDirect URL access to restricted routes, lazy-loaded module pathsBrowser, Burp Repeater
JavaScript bundlesAPI keys, internal URLs, hardcoded credentials, source mapsChrome DevTools, Semgrep, trufflehog
Client-side storageTokens in localStorage, sensitive data in sessionStorage/IndexedDBChrome DevTools (Application tab)
DOM XSSURL parameters to innerHTML sinks, hash fragment injectionManual testing, Burp DOM Invader
API authorizationToken swapping between roles, IDOR on API endpointsBurp Suite (Repeater, Autorize extension)
JWT handlingToken storage, expiration, algorithm confusion, excessive claimsjwt.io, Burp JWT extension
postMessageMissing origin validation, sensitive data in messagesConsole listener, manual review
Prototype pollution__proto__ injection in query params, merge operationsManual injection, browser console
Third-party librariesKnown CVEs in bundled dependenciesRetire.js, Snyk, npm audit
Webpack/build configSource maps exposed in production, debug mode enabledBrowser Network tab, sourceMappingURL check

Frequently Asked Questions

Can OWASP ZAP test single-page applications effectively?

ZAP’s default spider can’t handle SPAs because it doesn’t execute JavaScript. ZAP’s AJAX Spider, which uses a headless browser, performs better but still relies on heuristic clicking rather than understanding the application’s navigation structure. For basic coverage, configure the AJAX Spider with authentication and let it run, but expect to supplement with manual proxy-based testing for anything beyond surface-level findings.

Do I need source code access to pentest a SPA?

No. You can pentest a SPA entirely as a black-box exercise using browser DevTools, a proxy, and the JavaScript bundles the application serves to your browser. Source code access (or source maps) makes the job faster, especially for finding hardcoded secrets and tracing DOM XSS data flows, but it isn’t required.

Is localStorage always insecure for token storage?

localStorage is vulnerable to XSS because any JavaScript running on the page can read it. If your application has even one XSS vulnerability, tokens in localStorage are compromised. The more secure alternative is httpOnly cookies with SameSite attributes, which JavaScript can’t access. That said, some SPA architectures make cookies impractical (cross-origin API calls, for example), so the risk comes down to how confident you are in your XSS prevention.

How do you handle authentication during an automated SPA pentest?

The Strobes AI agent authenticates through the actual login form using credentials stored in the Credentials Vault. It fills in the username and password fields, clicks the login button, waits for the SPA to store the session token, and then maintains that authenticated state throughout the test. For multi-factor authentication, you can use the Local Browser Bridge on a machine where the MFA device (like a YubiKey) is available.

What is the biggest mistake teams make when pentesting SPAs?

Testing only the API and ignoring the client-side code. Many teams proxy the SPA’s API calls and test those endpoints for SQLi, IDOR, and auth bypass (which is necessary), but skip reviewing the JavaScript bundle for secrets, the client-side routing for authorization gaps, and the DOM for XSS sinks. The client-side code is part of the attack surface. Treat it that way.

How often should you pentest a SPA?

At minimum, run a test before every major release and quarterly for compliance. If you’re deploying weekly (most SPA teams are), continuous testing is the only approach that keeps pace. A scheduled weekly pentest catches new vulnerabilities within days of introduction instead of letting them sit in production for months until the next annual assessment.

Sources

  • OWASP Testing Guide v4.2 - Client-Side Testing
  • OWASP Top 10 - A03:2021 Injection (including DOM-based XSS)
  • PortSwigger - DOM-Based Vulnerabilities
  • State of JS 2024 Survey - Front-End Frameworks
  • NIST SP 800-95 - Guide to Secure Web Services
  • CWE-79: Improper Neutralization of Input During Web Page Generation
  • PortSwigger - Prototype Pollution
  • Auth0 - Token Storage Best Practices

Related Reading

  • Web Application Penetration Testing: Steps and Test Cases
  • Web Application Pentesting Checklist
  • Web Application Penetration Testing Tools
  • Agentic Pentesting: The Complete Guide
  • AI-Powered Pentesting: Crawling and Attack Surface Discovery
  • Understanding the OWASP Top 10 Application Vulnerabilities
Tags
SPA pentestingDOM XSSAI pentestingDASTApplication Security

Stop chasing vulnerabilities Start reducing exposure

See how Strobes AI agents validate and fix your most critical exposures automatically.

Book a Demo
Continue Reading

Related Posts

Bug bounty vs pentesting vs AI pentesting comparison featured image
Penetration TestingApplication Security

Bug Bounty vs. Pentesting vs. AI Pentesting: Which Model Fits Your AppSec Program?

Bug bounty vs pentesting vs AI pentesting: compare costs, coverage, compliance, and when to use each model. Build a layered AppSec testing strategy.

Jun 4, 202621 min
Pentesting in-house vs outsourcing comparison: cost, coverage, and the third option, AI pentesting
Penetration TestingPTaaS

Pentesting In-House vs. Outsourcing: Cost, Coverage, and the Third Option

Compare in-house vs outsourced pentesting on cost, coverage, and depth. Discover why AI pentesting is the third option that changes the math for security teams.

Jun 4, 202621 min
DAST vs pentesting vs AI pentesting comparison showing what each application security testing approach finds
Penetration TestingApplication Security

DAST vs. Pentesting vs. AI Pentesting: What Each One Actually Finds

Compare DAST, manual pentesting, and AI pentesting. Learn what each approach finds, misses, costs, and when to use each for full application security coverage.

Jun 4, 202622 min