Security Testing for Applications

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.

Identifying Security Risks Early

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.

Validating Authentication and Authorization

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:

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.

Testing for Common Vulnerabilities

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.

Validating Secure Configuration

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:

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"

Testing for Data Protection

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.

Ensuring Security in CI/CD

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.

Common Failure Modes

Security testing often fails silently. Key issues include:

These failures are caught only when tests are run against real deployments, not just local development environments.

Summary

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.