Security Testing for Web Applications: What to Test and How
A practical guide to security testing web applications — SAST, DAST, manual testing techniques, tools, and building security testing into your development workflow.

James Ross Jr.
Strategic Systems Architect & Enterprise Software Developer
Security Testing for Web Applications: What to Test and How
Security testing exists on a spectrum from fully automated to deeply manual, and from broad surface coverage to narrow targeted analysis. Most development teams need a combination of approaches to achieve meaningful coverage without security testing becoming a full-time job unto itself.
I am going to walk through the practical security testing approaches I use, the specific tools involved, and how to integrate them into a development workflow that does not slow your team down.
Static Application Security Testing (SAST)
SAST analyzes your source code for security vulnerabilities without executing it. It finds issues like SQL injection patterns, hardcoded secrets, insecure cryptographic functions, and use of known-vulnerable APIs — at code review time, before the code ships.
Semgrep is my preferred SAST tool for web applications. It uses pattern-based rules with a community library of security rules for JavaScript, TypeScript, Python, and most other languages. It integrates with CI and runs on every PR:
- name: Run Semgrep
uses: semgrep/semgrep-action@v1
with:
config: >-
p/javascript
p/typescript
p/nodejs
p/owasp-top-ten
generateSarif: "1"
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep.sarif
Uploading SARIF results to GitHub displays findings inline in the code review interface. Reviewers see security warnings in the same place they see test failures.
ESLint security plugins catch common security mistakes in JavaScript/TypeScript at the linter level — the fastest feedback loop:
npm install eslint-plugin-security eslint-plugin-no-unsanitized
{
"plugins": ["security", "no-unsanitized"],
"extends": ["plugin:security/recommended"],
"rules": {
"no-unsanitized/method": "error",
"no-unsanitized/property": "error"
}
}
The security plugin flags dangerous patterns like eval(), RegExp() with dynamic patterns (ReDoS vulnerability), and child process execution with unsanitized input. The no-unsanitized plugin catches uses of innerHTML and similar sinks.
CodeQL (GitHub's semantic code analysis) goes deeper than pattern matching — it builds a semantic model of your code and tracks data flow, finding tainted data flows from user input to dangerous sinks. It is more accurate than simple pattern matching and catches vulnerabilities that cross function boundaries:
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: javascript
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:javascript"
Dynamic Application Security Testing (DAST)
DAST tests your running application by sending malicious inputs and analyzing responses. It finds vulnerabilities that only manifest at runtime — configuration issues, server-side behavior that static analysis cannot see.
OWASP ZAP (Zed Attack Proxy) is the standard open-source DAST tool. Use it in two modes:
Automated scan in CI against your staging environment:
- name: ZAP Baseline Scan
uses: zaproxy/action-baseline@v0.12.0
with:
target: "https://staging.yourdomain.com"
rules_file_name: ".zap/rules.tsv"
cmd_options: "-a"
The baseline scan runs a quick automated test covering the most common vulnerabilities — reflected XSS, SQL injection in query parameters, security headers, and misconfiguration. It produces a report you can review as part of your deployment process.
Manual scan with ZAP as a proxy: configure your browser to use ZAP as an HTTP proxy, then manually use your application. ZAP records all requests and passively scans for vulnerabilities. After your manual session, run the active scanner on the captured requests to probe each endpoint for vulnerabilities.
Nuclei is a fast, template-based scanner with a community library covering thousands of specific vulnerability patterns:
# Install
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
# Run against staging
nuclei -u https://staging.yourdomain.com \
-t /nuclei-templates/http/ \
-tags owasp,web,exposure \
-severity medium,high,critical
Nuclei is excellent for checking for specific misconfigurations and exposure (exposed .env files, debug endpoints, default credentials on admin panels).
Manual Security Testing Techniques
Automated tools miss context-specific vulnerabilities, business logic flaws, and multi-step attack chains. Manual testing fills this gap.
Broken access control testing — the most common vulnerability class and one that automated tools frequently miss. For every API endpoint that returns data, test whether you can access other users' data by changing ID parameters. Use two test accounts and verify that Account A cannot access Account B's resources.
The test is systematic: create test accounts, log in as Account A, make note of your resource IDs, log out, log in as Account B, attempt to access Account A's resources using the IDs you noted. If it succeeds, you have broken access control.
Authentication bypass testing — test your authentication logic for edge cases. Can you access authenticated endpoints without a session cookie? With an expired token? With a token from a different environment? With a token for a deleted user?
Input validation fuzzing — send unexpected inputs to every form field and API parameter. A simple manual approach:
- Send an empty string where a value is required
- Send a very long string (10,000+ characters)
- Send SQL metacharacters:
' " ; -- /* */ - Send HTML:
<script>alert(1)</script> - Send Unicode edge cases: null bytes (
\x00), emoji, RTL text - Send type mismatches: a string where a number is expected, an array where a string is expected
- Send negative numbers where positive is expected
- Send extremely large numbers
Watch for 500 errors (indicating unhandled input), 403/401 changes (authorization behavior changes), unexpected 200 responses (input accepted when it should be rejected), or response content changes (data leakage).
Business logic testing — think about the specific workflows in your application and how they could be abused. Can a discount code be applied multiple times? Can a user cancel an order after it ships and receive a refund? Can a user upgrade their account tier without paying by manipulating a parameter? These are not generic vulnerability patterns — they require understanding your specific application.
Integrating Security Testing Into Your Workflow
The goal is security testing that is fast enough and automated enough that it does not create friction for developers, while still catching meaningful issues.
At commit time: ESLint security rules and pre-commit hooks run in milliseconds. Catch obvious anti-patterns before they reach a PR.
At PR review: SAST (Semgrep, CodeQL) runs on every PR via GitHub Actions. Results appear inline in the code review. This is the highest-value automated security testing because it runs on every change.
Pre-deployment: ZAP baseline scan runs against staging before each production deployment. Block deployments where new high-severity findings appear.
Weekly: Nuclei scan against staging, scheduled SAST scan with broader rule sets, dependency vulnerability scan.
Quarterly: Manual penetration testing session against the full application. This does not need to be a professional pentest — an internal security-focused code review and manual testing session by team members who understand security is valuable and much cheaper.
Annually: Professional penetration test by an external firm. Important for compliance and for catching vulnerabilities that your internal team has blind spots to. Budget this as part of your security program.
Making Sense of Findings
Not every finding from automated tools is a real vulnerability. False positives are common in SAST and require human judgment to evaluate. For each finding:
- Understand what the tool thinks the vulnerability is
- Trace the code path to understand the actual risk
- Determine if it is exploitable in your application's context
- Fix it or document why you accepted the risk
Build a security findings registry. Every finding gets a status (open, in progress, accepted risk, false positive) and a rationale. This creates accountability and ensures findings do not disappear into a backlog and get forgotten.
If you want help setting up a security testing program for your application or want a professional security review, book a session at https://calendly.com/jamesrossjr.