Skip to content
SDETRole
SDETRole

Lesson 3 of 9 · Advanced Quality Engineering

Security Testing Basics

Security testing for QA engineers: ethics and authorisation, the OWASP Top 10:2025, SAST, DAST and SCA, and security checks you can automate today.

intermediate 7 min read Updated

You will learn to

  • Explain the legal and ethical boundaries of security testing
  • Summarise the OWASP Top 10:2025 categories and how a tester probes them
  • Distinguish SAST, DAST, SCA and manual security testing
  • Write automated API tests for access control and security headers

Prerequisites

  • HTTP fundamentals
  • API testing basics

Security is a quality attribute like performance or usability, and testers are well placed to find security bugs: you already think about unexpected inputs, broken assumptions and the paths nobody designed for. This lesson covers the ground rules, a shared vocabulary and checks you can add to your existing suites. It does not turn you into a penetration tester, but it makes you a much more useful partner to one.

Ground rules: authorisation and ethics

Practical rules for testers:

  • Use dedicated test accounts and synthetic data. Never use real customer data to “see what happens”.
  • Stop and report as soon as you confirm a serious issue. Do not extract more data than needed to prove it.
  • Report security findings through the agreed private channel, not a public issue tracker.
  • Keep tools and payloads within scope: an automated scanner pointed at the wrong host is still unauthorised testing.

The OWASP Top 10:2025

The OWASP Top 10 is an awareness document listing broad categories of the most critical web application security risks. The current edition is the OWASP Top 10:2025:

ID Category What a tester probes
A01 Broken Access Control Can user A read or change user B’s data by changing an ID? Can a normal user call admin endpoints? Can a URL input make the server fetch internal resources (SSRF)?
A02 Security Misconfiguration Default credentials, debug endpoints, directory listing, missing security headers.
A03 Software Supply Chain Failures Are dependencies pinned, locked and scanned? Is there an SBOM? Are CI permissions locked down?
A04 Cryptographic Failures Is sensitive data sent over plain HTTP, stored unencrypted, or exposed in responses?
A05 Injection Do SQL, NoSQL, OS command or template inputs get interpreted? Includes cross-site scripting (XSS).
A06 Insecure Design Are there missing controls by design, such as no rate limit on password reset?
A07 Authentication Failures Weak password rules, no lockout or throttling, session IDs that survive logout.
A08 Software or Data Integrity Failures Unsigned updates, insecure deserialisation, client-supplied data trusted without checks.
A09 Security Logging and Alerting Failures Are failed logins and access violations logged and alerted on?
A10 Mishandling of Exceptional Conditions Do errors leak stack traces? Does the system fail closed when something breaks halfway?

Compared with 2021, two categories are new (Software Supply Chain Failures and Mishandling of Exceptional Conditions), SSRF has been folded into Broken Access Control, and several categories were renamed or reordered. OWASP revises the list every few years, so check top10.owasp.org before quoting it in a test strategy. The categories are deliberately broad; for detailed test procedures, the OWASP Web Security Testing Guide (WSTG) and the Application Security Verification Standard (ASVS) are the reference documents. The OWASP Top 10 for Testers lesson goes category by category.

Types of security testing

  • SAST (static application security testing): analyses source code without running it. Finds patterns such as string-concatenated SQL. Runs early, in the IDE or on PRs. Produces false positives that need triage.
  • DAST (dynamic application security testing): attacks a running application from the outside, like a user would. OWASP ZAP is a widely used open-source example. Finds runtime issues such as missing headers or reflected input, but only on the pages and requests it can reach.
  • SCA (software composition analysis): checks your dependencies against databases of known vulnerabilities. npm audit and GitHub Dependabot alerts are everyday examples.
  • Secret scanning: detects credentials committed to repositories.
  • Manual testing and penetration testing: humans reasoning about business logic, chaining small issues into real attacks. No scanner understands that “a discount code can be applied after payment” is a vulnerability.

Each catches different classes of bug. A mature pipeline uses several, and testers add the business-logic perspective that tools lack.

Security checks you can automate today

You do not need specialised tools to start. Your API and UI test frameworks can encode security expectations as regression tests.

Broken access control (IDOR)

Insecure direct object references are among the most common and most damaging bugs, and they are easy to test: authenticate as one user and request another user’s resource.

import { test, expect } from '@playwright/test';

test('a user cannot read another user\'s order', async ({ playwright }) => {
  const alice = await playwright.request.newContext({
    baseURL: process.env.BASE_URL,
    extraHTTPHeaders: { Authorization: `Bearer ${process.env.ALICE_TOKEN}` },
  });

  // Order 1002 belongs to Bob in the seeded test data
  const res = await alice.get('/api/orders/1002');

  // 404 avoids confirming the order exists; 403 is also acceptable if that is the agreed contract
  expect([403, 404]).toContain(res.status());
  await alice.dispose();
});

test('a regular user cannot call admin endpoints', async ({ playwright }) => {
  const alice = await playwright.request.newContext({
    baseURL: process.env.BASE_URL,
    extraHTTPHeaders: { Authorization: `Bearer ${process.env.ALICE_TOKEN}` },
  });
  const res = await alice.delete('/api/admin/users/1003');
  expect(res.status()).toBe(403);
  await alice.dispose();
});

Repeat the pattern for every verb (GET, PUT, PATCH, DELETE) and for unauthenticated requests, which should get 401.

Security headers and cookies

test('responses include baseline security headers', async ({ request }) => {
  const res = await request.get('/');
  const headers = res.headers(); // header names are lower-cased

  expect(headers['strict-transport-security']).toBeDefined();
  expect(headers['x-content-type-options']).toBe('nosniff');
  expect(headers['content-security-policy']).toBeDefined();
});

test('session cookie is HttpOnly, Secure and SameSite', async ({ page, context }) => {
  await page.goto('/login');
  // ...log in with a test account...
  const cookies = await context.cookies();
  const session = cookies.find((c) => c.name === 'session');
  expect(session?.httpOnly).toBe(true);
  expect(session?.secure).toBe(true);
  expect(session?.sameSite).not.toBe('None');
});

Agree the exact header values with your security team; these tests encode the policy so regressions are caught.

Input handling

Feed hostile but harmless strings into inputs and check they are treated as data:

<script>alert(1)</script>
"><img src=x onerror=alert(1)>
' OR '1'='1
../../etc/passwd
{{7*7}}

What you assert is that the application does not interpret them: the script does not execute, the text is displayed escaped, the query does not return extra rows, and {{7*7}} is not rendered as 49. A 500 error on these inputs is also a finding, since it often means the input reached somewhere it should not.

Authentication behaviour

  • Repeated failed logins trigger throttling or lockout.
  • Error messages do not reveal whether the username exists.
  • Logging out invalidates the session on the server (replay the old cookie or token afterwards).
  • Password reset tokens are single-use and expire.

Integrating with the pipeline

A realistic starting point for a team:

  1. On every PR: SCA (npm audit --audit-level=high or Dependabot), secret scanning and your automated access-control and header tests.
  2. Nightly: a DAST baseline scan against a staging environment you own.
  3. Per release or quarter: manual or third-party penetration testing focused on new features.

Tune scanners before making them blocking; a gate that fails on noise gets disabled.

Practice exercise

An API has GET /api/invoices/:id, PATCH /api/invoices/:id and GET /api/invoices (list). Users belong to companies and must only see their own company’s invoices. Admin users can see all invoices within their company. Design a set of security test cases for access control.

Show a model answer

Set up seeded data: Company A with a regular user (A-user) and an admin (A-admin); Company B with a regular user (B-user); invoices A-1 and B-1.

# Actor Request Expected
1 No token GET /api/invoices/A-1 401
2 A-user GET /api/invoices/A-1 200
3 A-user GET /api/invoices/B-1 403 or 404, no data
4 A-user PATCH /api/invoices/B-1 403 or 404, B-1 unchanged
5 A-admin GET /api/invoices/B-1 403 or 404 (admin is company-scoped)
6 A-user GET /api/invoices Only Company A invoices in the list
7 A-user GET /api/invoices?companyId=B Filter ignored or rejected; no B data
8 Expired or tampered token Any 401
9 A-user PATCH /api/invoices/A-1 with body changing companyId to B Rejected or field ignored (mass assignment)

For cases 4 and 9, verify state afterwards with a separate authorised request, not just the status code. Run the suite on every PR because access control regresses easily when new endpoints are added.

Key takeaways

  • Only test what you are authorised to test, within an agreed scope, and report findings privately.
  • The OWASP Top 10:2025 gives shared vocabulary; WSTG and ASVS give detailed test guidance.
  • SAST, DAST, SCA, secret scanning and manual testing each catch different classes of bug.
  • Access control (IDOR) tests, header and cookie checks and hostile-input checks fit in your existing frameworks.
  • Assert that input is treated as data, and verify state after attempted unauthorised changes.
  • Every security finding should become an automated regression test.

Key terms

Finished this lesson?

Progress is saved in this browser. No account needed.

Type to search across SDETRole.

Full-text search by Pagefind · runs in your browser Open search page →