Entering a single quote ' in any text field and watching for a database error is a basic SQL injection check that takes ten seconds and requires no security tools. Swapping another user's resource ID in the URL to see if you can access it catches broken object-level authorization, one of the most common API vulnerabilities, and it's already within QA's scope during functional testing. This article covers the authentication, authorization, input validation, and cookie checks QA engineers can run without specialists, with Playwright examples for the ones worth automating.

What QA engineers can test without specialized tools

Authentication and session management

The most common security bugs are in auth flows. Test these during regular functional testing:

Brute force protection: Try submitting the wrong password 10 times. Does the app lock the account or rate-limit? If not, that's a finding. Session fixation: Log in. Copy the session cookie. Log out. Try using the old session cookie. If it still works, sessions aren't invalidated on logout. "Remember me" lifetime: Set "remember me", close the browser, reopen after 30 days (or set your system clock forward). Does the session still work? For how long? Password reset token reuse: Request a reset email. Use the link to reset. Try the link again. Should be invalid. If it works twice, it's a bug. Concurrent sessions: Log in on two different browsers simultaneously. Should either session be invalidated?

Authorization (access control)

This is where most serious bugs hide:

Horizontal privilege escalation: Create two accounts (user A and user B). As user A, create some resource (an order, a document, a profile item). Note the resource ID in the URL. Log in as user B. Try to access, edit, or delete user A's resource by ID. If you can — that's a broken object-level authorization bug (BOLA), one of the most common API vulnerabilities.

GET /api/orders/12345   ← user A's order

Log in as user B and hit the same URL. What happens?

Vertical privilege escalation: Try accessing admin features as a regular user. Check admin URLs (/admin, /dashboard/admin, /api/admin/*) without admin credentials. IDOR in API responses: Does the API return only data the current user should see? Check if a regular user's API call ever returns other users' data in any field.

Input validation

Every form field is a potential injection point:

SQL injection (basic test): Enter ' (single quote) in any text field. Does the app return a database error? If yes, it's likely vulnerable. XSS (basic test): Enter in any field that displays back to users (name fields, comments, addresses). If an alert box appears, it's stored XSS. Path traversal: Try ../../../etc/passwd in file name fields or URL parameters. Should return an error, not file contents.

These manual tests are quick and catch the low-hanging fruit. They don't replace automated security scanning but they're worth running on every new feature.

HTTPS and cookies

Check in browser DevTools:

  • Is the site served over HTTPS? Does HTTP redirect to HTTPS?
  • Are session cookies marked HttpOnly (not accessible from JavaScript)?
  • Are session cookies marked Secure (only sent over HTTPS)?
  • Is SameSite set to Strict or Lax on auth cookies?

In Playwright:

test('auth cookie has security flags', async ({ page, context }) => {
  await page.goto('/login');
  // ... perform login
  
  const cookies = await context.cookies();
  const sessionCookie = cookies.find(c => c.name === 'session_token');

  expect(sessionCookie?.httpOnly).toBe(true);
  expect(sessionCookie?.secure).toBe(true);
  expect(sessionCookie?.sameSite).toMatch(/strict|lax/i);
});

Error messages and information disclosure

Error messages that are too helpful are a security issue:

  • "User not found" vs "Invalid credentials": the first tells attackers which accounts exist
  • Stack traces in production responses
  • Internal server paths, database names, or library versions in error messages
  • API responses that include fields like passwordHash, internalId, or admin flags

Integrating security into your test process

OWASP ZAP baseline scan

OWASP ZAP is free and can run as a passive scanner alongside your Playwright tests. The baseline scan mode crawls your app and flags obvious issues:

# .github/workflows/security.yml
- name: ZAP Baseline Scan
  uses: zaproxy/action-baseline@v0.10.0
  with:
    target: 'https://staging.myapp.com'
    rules_file_name: '.zap/rules.tsv'

This catches things like missing security headers, insecure cookies, and exposed server information — without you writing any test code.

Security headers check

Headers your app should send:

Content-Security-Policy: default-src 'self'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Strict-Transport-Security: max-age=31536000; includeSubDomains
Referrer-Policy: strict-origin-when-cross-origin

Test them in Playwright:

test('security headers are present', async ({ request }) => {
  const response = await request.get('https://myapp.com');
  const headers = response.headers();

  expect(headers['x-content-type-options']).toBe('nosniff');
  expect(headers['x-frame-options']).toBe('DENY');
  expect(headers['strict-transport-security']).toContain('max-age=');
});

What QA engineers should hand to security specialists

Some security testing requires specialized skills and tools:

  • Full penetration testing
  • Binary analysis
  • Cryptography review
  • Infrastructure security (cloud config, network segmentation)
  • Social engineering testing

When you find a potential security issue through manual testing, document it clearly (reproduction steps, potential impact) and escalate to your security team or file it as a high-severity bug. Don't try to prove exploitability yourself — your job is to find it, not to exploit it.

The best security posture is defense in depth: QA catching the obvious issues during development, automated scanners catching the systematic issues in CI, and specialists doing the deep review before major releases.

→ See also: Security Testing for QA Engineers: What You Need to Know | The Software Development Life Cycle for QA Engineers | Risk-Based Testing: Prioritizing What to Test When You Can't Test Everything