Skip to main content

End-to-End Security Testing for React Applications

Security testing is not optional—it is a mandatory phase of application development. Many React developers skip security testing because they assume their framework handles all vulnerabilities or because they lack the expertise to test effectively. In reality, the React framework prevents only a small subset of attacks (like reflected XSS). You must actively test for injection, broken authentication, CSRF, logic flaws, and dependency vulnerabilities using automated tools and manual techniques. This guide covers four categories of security testing: static analysis (SAST), dynamic analysis (DAST), dependency scanning, and manual penetration testing.

1. Static Application Security Testing (SAST)

SAST tools analyze your source code for vulnerabilities without running it. They find hardcoded secrets, unsafe patterns, and common mistakes.

SonarQube and SonarCloud

SonarQube scans React code for security issues, code smells, and bugs:

# Install SonarQube Scanner
npm install -D sonarqube-scanner

# Create sonar-project.properties
projectKey=my-react-app
projectName=My React App
sources=src
exclusions=**/*.test.ts,**/node_modules/**
// package.json
"scripts": {
"sonar": "sonarqube-scanner"
}

Run it in your CI/CD pipeline:

# GitHub Actions
name: SonarQube Analysis
on: [push, pull_request]
jobs:
sonarqube:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: sonarsource/sonarqube-scan-action@master
env:
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

SonarQube flags issues like:

  • Hardcoded secrets: const API_KEY = "sk_live_123"
  • Use of eval: eval(userInput)
  • SQL injection patterns: const query = "SELECT * FROM users WHERE id=" + userId

ESLint Security Plugins

Install security-focused ESLint plugins:

npm install -D eslint-plugin-security eslint-plugin-no-unsanitized

Configure ESLint:

// .eslintrc.json
{
"extends": ["plugin:security/recommended", "plugin:no-unsanitized/DOM"],
"plugins": ["security", "no-unsanitized"],
"rules": {
"security/detect-object-injection": "warn",
"security/detect-eval-with-expression": "error",
"no-unsanitized/method": "error"
}
}

ESLint now warns on:

// Flagged as dangerous
eval(code); // security/detect-eval-with-expression
<div dangerouslySetInnerHTML={{ __html: userInput }} /> // no-unsanitized

2. Dependency Scanning

Automated tools check your npm dependencies for known vulnerabilities.

npm audit (Built-in)

# Scan for vulnerabilities
npm audit

# Fix automatically where possible
npm audit fix

# Fix major versions (breaking changes possible)
npm audit fix --force

Output example:

┌─────────────────────────────────────────────────────────┐
│ 2 vulnerabilities (1 moderate, 1 critical) │
├─────────────────────────────────────────────────────────┤
│ Critical: Denial of Service in lodash │
│ Affects: lodash < 4.17.21 │
│ Fixed: lodash >= 4.17.21 │
└─────────────────────────────────────────────────────────┘

Snyk

Snyk provides more detailed vulnerability information and continuous monitoring:

# Install and authenticate
npm install -g snyk
snyk auth

# Scan project
snyk test

# Monitor for new vulnerabilities
snyk monitor

Dependabot (GitHub)

Enable Dependabot in your GitHub repository settings to automatically open pull requests for dependency updates:

# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
security-updates-only: true # Only alert on security updates

3. Dynamic Application Security Testing (DAST)

DAST tools run your application and test it for vulnerabilities by making requests, fuzzing inputs, and observing responses.

OWASP ZAP (Zed Attack Proxy)

ZAP is a free, open-source security scanner:

# Install Docker image
docker pull owasp/zap2docker-stable

# Run a scan
docker run -v $(pwd):/zap/wrk:rw -t owasp/zap2docker-stable zap-baseline.py -t http://localhost:3000 -r report.html

ZAP automatically checks for:

  • XSS vulnerabilities
  • SQL injection
  • Missing security headers
  • Insecure SSL/TLS configuration
  • Insecure deserialization

Burp Suite Community

Burp Suite is a professional penetration testing tool (free community edition available):

  1. Download Burp Suite Community
  2. Configure your browser to use Burp as a proxy (127.0.0.1:8080)
  3. Browse your React app normally
  4. Use Burp's Scanner to automatically find vulnerabilities
  5. Manually test edge cases

4. Manual Penetration Testing

Automated tools find common vulnerabilities, but manual testing finds logic flaws and edge cases.

Security-Focused Test Cases

Create a test plan for common React vulnerabilities:

// test/security.test.js

describe("Security Tests", () => {
test("XSS: User input is escaped", () => {
const payload = "<img src=x onerror=alert('xss')>";
render(< Comment text={payload} />);
expect(screen.queryByText(/onerror/)).not.toBeInTheDocument();
// Verify the payload is rendered as text, not HTML
});

test("CSRF: State-changing requests include CSRF token", async () => {
const response = await fetch("/api/transfer", {
method: "POST",
body: JSON.stringify({ amount: 100 })
// No CSRF token
});
expect(response.status).toBe(403);
});

test("Auth: API returns 401 for unauthenticated requests", async () => {
const response = await fetch("/api/profile", {
credentials: "omit" // No cookies
});
expect(response.status).toBe(401);
});

test("Auth: Users cannot access other users' data", async () => {
// Login as user A
await login("[email protected]", "password");

// Try to fetch user B's data
const response = await fetch("/api/users/userB");
expect(response.status).toBe(403);
});

test("Injection: SQL injection attempt is blocked", async () => {
const payload = "'; DROP TABLE users; --";
const response = await fetch("/api/search?q=" + encodeURIComponent(payload));

// Should not delete the users table
const usersAfter = await User.count();
expect(usersAfter).toBeGreaterThan(0);
});
});

Manual Testing Checklist

TestMethodExpected Result
XSS: Script injectionInject <script>alert('xss')</script>Script should not execute
CSRF: Forged requestPOST to endpoint from attacker's siteRequest should fail (403)
Auth: Missing tokenCall protected API without credentialsShould return 401
Auth: Expired tokenUse an old/revoked tokenShould return 401
Access Control: User A accesses User B's dataChange URL ID to another user's IDShould return 403
Rate limiting: Brute forceSend 100 login attempts in 1 secondShould throttle requests
Input validation: Negative amountSubmit negative number in payment formShould be rejected
Logic bypass: Skip payment stepJump to confirmation without payingShould redirect to payment

Integrating Security Testing in CI/CD

Automate security testing in your CI/CD pipeline:

# GitHub Actions
name: Security Tests
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Install dependencies
run: npm install

- name: SAST: SonarQube
uses: sonarsource/sonarqube-scan-action@master
env:
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

- name: Dependency Scan
run: npm audit --audit-level=moderate

- name: ESLint Security
run: npm run lint

- name: Snyk Test
run: npx snyk test
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

- name: Build
run: npm run build

- name: Unit Tests
run: npm test

- name: DAST: OWASP ZAP
run: |
docker run -v $(pwd):/zap/wrk:rw -t owasp/zap2docker-stable \
zap-baseline.py -t http://localhost:3000 -r report.html

- name: Upload ZAP Report
uses: actions/upload-artifact@v3
if: always()
with:
name: zap-report
path: report.html

Vulnerability Severity Levels

When security tests find issues, prioritize fixes by severity:

SeverityCVSS ScoreExampleTimeline
Critical9.0–10.0Unauthenticated RCE, data breachFix immediately
High7.0–8.9Authenticated RCE, privilege escalationFix within 7 days
Medium4.0–6.9XSS, CSRF, logic flawFix within 30 days
Low0.1–3.9Information disclosureFix within 90 days

Post-Breach Response

If security testing or monitoring discovers a vulnerability:

  1. Assess impact: How many users are affected? What data is exposed?
  2. Contain: Disable the vulnerable feature if possible.
  3. Fix: Patch the vulnerability and test thoroughly.
  4. Deploy: Release the fix as soon as possible.
  5. Notify: Inform affected users and relevant authorities (if required by law).
  6. Investigate: Determine how the vulnerability was introduced and prevent recurrence (code review, testing, training).

Key Takeaways

  • Security testing is not optional; integrate it into your development workflow.
  • Use SAST tools (SonarQube, ESLint) to catch vulnerabilities in code.
  • Scan dependencies regularly (npm audit, Snyk, Dependabot).
  • Use DAST tools (ZAP, Burp) to test the running application.
  • Perform manual penetration testing to find logic flaws and edge cases.
  • Automate testing in CI/CD pipelines.
  • Prioritize fixes by severity and respond quickly to critical vulnerabilities.

Frequently Asked Questions

How often should I run security tests?

Run automated tests (SAST, dependency scanning) on every commit. Run DAST before every production release. Perform manual penetration testing quarterly or before major releases.

Can automated tools find all vulnerabilities?

No. Automated tools find common, well-known patterns. Manual testing and penetration testing find logic flaws, business logic bypasses, and novel attacks. Combine both approaches.

What is a CVSS score, and why does it matter?

CVSS (Common Vulnerability Scoring System) is a standardized way to rate vulnerability severity (0–10). A score of 9+ is critical and needs immediate attention. Use CVSS scores to prioritize fixes.

Should I use free tools or commercial tools?

Start with free tools (npm audit, Snyk, ZAP, ESLint). As your app grows, consider commercial tools for better reporting and integrations. Most free tools are sufficient for small teams.

How do I handle third-party vulnerabilities I cannot fix?

If a dependency has a vulnerability you cannot patch (e.g., the maintainer is inactive), consider: alternatives (switch to a maintained library), workarounds (disable the vulnerable feature), or isolation (run the dependency in a sandbox). Last resort: accept the risk if the vulnerability is low-severity and unexploitable in your context.

Further Reading