Build comprehensive security testing into your development workflow. Covers SAST, DAST, dependency scanning, penetration testing, and security test automation in CI/CD pipelines.
Security testing for applications ensures that software behaves correctly under attack, protects data, enforces access control, and resists common vulnerabilities. It matters at every stage of the development lifecycle, from pre-commit checks to production deployments, and is critical for systems handling sensitive data, user identities, or financial transactions.
Security testing begins with identifying potential threats and mapping them to application behavior. The most effective way to start is by defining a threat model using the STRIDE framework: Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, and Elevation of Privilege.
To capture threats systematically, maintain a threat registry as a JSON file, where each threat includes a unique ID, a description, the affected component, and an associated risk score.
{
"threats": [
{
"id": "STRIDE-001",
"type": "Spoofing",
"description": "An attacker impersonates a trusted user via fake credentials.",
"component": "Authentication Service",
"risk": 8
},
{
"id": "STRIDE-007",
"type": "Information Disclosure",
"description": "API endpoints expose internal user IDs in JSON responses.",
"component": "User Profile API",
"risk": 6
}
]
}
This registry is used to drive test selection. For example, a CI pipeline runs npx security-test --threats ./threats.json --baseline ./baseline-scan.json to generate a security test suite tailored to the current threat landscape.
Authentication and authorization are the foundation of application security. Testing these requires verifying that users can log in, tokens are issued correctly, and permissions are enforced.
To validate JWT-based authentication, use curl to send a login request and inspect the response:
curl -X POST https://api.example.com/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "alice", "password": "secret"}' \
-v
The expected response includes a JWT in the Authorization header and a Set-Cookie header:
HTTP/1.1 200 OK
Content-Type: application/json
Set-Cookie: session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbGljZSIsImV4cCI6MTY3OTQyMjAwMCwiaWF0IjoxNjc5NDIxNzAwfQ.Sk13T3i5J5L9iO1Zv7N6dY6FvX5g5Lq3YKjv3Km6sFk; Path=/; HttpOnly; Secure; SameSite=Lax
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbGljZSIsImV4cCI6MTY3OTQyMjAwMCwiaWF0IjoxNjc5NDIxNzAwfQ.Sk13T3i5J5L9iO1Zv7N6dY6FvX5g5Lq3YKjv3Km6sFk
A test script checks that:
HS256 and contains a sub claim.exp claim is within 10 minutes of the current time.Set-Cookie value is correctly formatted and includes HttpOnly, Secure, and SameSite=Lax.Authorization header includes the Bearer scheme.If any check fails, the test outputs a detailed error:
{
"test": "JWT token validation",
"status": "failed",
"errors": [
{
"field": "token.exp",
"expected": "within 10 minutes of now",
"actual": "15 minutes ago",
"details": "Token expired at 2023-03-15T12:00:00Z, now is 2023-03-15T12:15:00Z"
},
{
"field": "cookie.SameSite",
"expected": "Lax",
"actual": "Strict",
"details": "Cookie set with SameSite=Strict, but expected Lax"
}
]
}
For authorization testing, send requests with different roles and verify that the response includes role-specific data.
curl -H "Authorization: Bearer $JWT" \
-H "Accept: application/json" \
https://api.example.com/users/me
The response must include a role field, and endpoints like /admin/users must return 403 Forbidden for non-admin users.
Security testing must validate the presence and effectiveness of defenses against OWASP Top 10 vulnerabilities. The most common are:
To test SQL injection, send a crafted username parameter:
curl -X POST https://api.example.com/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "admin\' OR 1=1--", "password": "x"}'
The server should respond with a successful login, indicating that the input was fully evaluated as SQL. The test verifies this by checking the database logs or by asserting that the response includes a role field with value admin.
For XSS, submit a script payload in a user profile:
{
"name": "Alice",
"bio": "<script>alert(document.cookie)</script>"
}
After saving, fetch the profile and confirm that the script executes in the browser:
curl https://app.example.com/users/alice
The response must include <script>alert(document.cookie)</script> and, when parsed by a browser, trigger an alert dialog with the current session cookie.
To test CSRF, create a form that includes a hidden csrf_token field:
<form action="/transfer" method="POST">
<input type="hidden" name="csrf_token" value="abc123">
<input type="number" name="amount" value="100">
<button type="submit">Transfer</button>
</form>
Send the form from a different origin using curl with Origin and Cookie headers:
curl -X POST https://api.example.com/transfer \
-H "Origin: https://evil.com" \
-H "Cookie: session=xyz789" \
-H "Content-Type: application/json" \
-d '{"amount": 100, "csrf_token": "abc123"}'
A successful transfer returns HTTP 200, while a mismatched or missing token returns 403 Forbidden.
For IDOR, access a resource using a direct ID, then modify it without authorization:
curl -H "Authorization: Bearer $ALICE_TOKEN" \
https://api.example.com/users/123
# User Bob tries to access Alice’s profile
curl -H "Authorization: Bearer $BOB_TOKEN" \
https://api.example.com/users/123
The test checks that Bob can read Alice’s profile but cannot update it, and that updating the profile returns 403 for users without edit:profile permission.
Applications often ship with default configurations that are insecure. Security testing includes validating that the system is correctly configured.
A common failure is missing or misconfigured security headers in HTTP responses. Use curl to check the response headers from a /health endpoint:
curl -I https://app.example.com/health
Expected headers include:
Strict-Transport-Security: max-age=63072000; includeSubDomains; preloadX-Content-Type-Options: nosniffX-Frame-Options: DENYContent-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';Referrer-Policy: strict-origin-when-cross-originPermissions-Policy: geolocation=(), camera=(), microphone=()A test script compares the actual response against an expected configuration:
{
"expected_headers": {
"Strict-Transport-Security": "max-age=63072000; includeSubDomains; preload",
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY"
},
"additional_checks": [
{
"header": "Content-Security-Policy",
"match": "script-src 'self' 'unsafe-inline'"
}
]
}
If any header is missing or misconfigured, the test logs the discrepancy.
Another common issue is file uploads with incorrect permissions. Test that uploaded files are served with the correct MIME types and access control:
curl -H "Authorization: Bearer $JWT" \
-F "[email protected]" \
https://api.example.com/files/upload
The response includes a url field. Fetch that URL and verify that the Content-Type header is application/pdf, and that the file is accessible without authentication.
curl -I https://cdn.example.com/files/123.pdf
Expected output:
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Length: 153248
Last-Modified: Wed, 15 Mar 2023 10:00:00 GMT
ETag: "abc123"
Applications must protect data at rest and in transit. Testing includes validating encryption, tokenization, and data masking.
To test encryption, send a request with sensitive data and verify that it is encrypted:
{
"name": "Alice Smith",
"email": "[email protected]",
"ssn": "123-45-6789",
"credit_card": "4111-1111-1111-1111"
}
The backend should store this in a database with encrypted columns. A test queries the database directly:
SELECT name, email, ssn, credit_card FROM users WHERE id = 123;
The result shows that ssn and credit_card are not stored in plaintext:
name | email | ssn | credit_card
-----------|-------------------|-------------|----------------
Alice Smith| [email protected]| 8B7E3C9A5F6C | 5D2F4C1E8A3B
To verify that data is properly masked, test a user list endpoint:
curl https://api.example.com/users
The response includes masked data:
[
{
"id": 123,
"name": "Alice Smith",
"email": "[email protected]",
"ssn": "123-XX-6789",
"credit_card": "4111-XXXX-XXXX-1111"
}
]
A test verifies that all sensitive fields are masked using email, ssn, and credit_card patterns.
Security testing must be integrated into the CI/CD pipeline. A typical workflow uses git, docker, and systemctl to deploy and test security.
# .github/workflows/security.yml
name: Security Testing
on: [push, pull_request]
jobs:
security-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build application
run: docker build -t app:latest .
- name: Run security tests
run: |
docker run --rm app:latest \
npx security-test \
--config ./test-config.json \
--report ./reports/security-report.json
- name: Deploy to staging
run: |
systemctl restart app-service
systemctl status app-service --no-pager
curl -f https://staging.example.com/health
The test-config.json file defines the test suite, including threat IDs, test scripts, and expected outcomes.
{
"threats": ["STRIDE-001", "STRIDE-007", "SQLi-001"],
"test_scripts": [
"tests/auth.js",
"tests/xss.js",
"tests/csrf.js"
],
"reporting": {
"format": "json",
"output": "reports/security-report.json"
}
}
The security-test CLI command reads this file, runs each test script, and generates a report that includes a summary, detailed results, and a list of failures.
When a test fails, the pipeline marks the PR with a status check, and sends a notification with the failure details.
Security testing often fails silently. Key issues include:
SameSite in cookies: Without SameSite=Lax, CSRF attacks succeed even when the application thinks it’s protected.Content-Type in file uploads: Uploading a PDF results in text/plain instead of application/pdf, leading to rendering issues.CORS: The Access-Control-Allow-Origin header is set to *, but the application expects Origin to be included.{"error": "Invalid token"} in some places, but {"message": "Token invalid"} in others, causing client-side bugs.These failures are caught only when tests are run against real deployments, not just local development environments.
Security testing for applications is not a checklist, but a continuous process of validation. It requires attention to detail in authentication, authorization, input validation, configuration, and data protection. By using standard tools and well-defined patterns, teams can build robust, repeatable tests that catch vulnerabilities early and ensure that security is not an afterthought, but a core part of the delivery pipeline.
This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy