Security Testing Guide
Testing Approach Comparison
| Type | Full Name | How It Works | Best Tools |
|---|---|---|---|
| SAST | Static Application Security Testing | Analyzes source code without running it | Semgrep, CodeQL, Checkmarx, SonarQube |
| DAST | Dynamic Application Security Testing | Attacks running application (black-box) | OWASP ZAP, Burp Suite, Nuclei |
| IAST | Interactive Application Security Testing | Agent inside app monitors during testing | Contrast Security, Seeker |
| SCA | Software Composition Analysis | Scans dependencies for known CVEs | Snyk, Dependabot, OWASP Dependency-Check |
| Secret Scanning | Detect credentials in code/history | Pattern matching in git history and files | GitLeaks, TruffleHog, GitHub Secret Scanning |
CI Security Pipeline
# GitHub Actions security checks
name: Security
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
# 1. Secrets detection (runs first โ fastest fail)
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history for secret scanning
- name: Scan for secrets
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# 2. Dependency vulnerability scanning
- name: Run Snyk
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
# 3. SAST โ Semgrep
- name: Semgrep SAST
uses: semgrep/semgrep-action@v1
with:
config: p/owasp-top-ten p/nodejs
# 4. Container image scanning
- name: Trivy image scan
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
severity: CRITICAL,HIGH
exit-code: 1
Security Regression Tests
// Write security tests alongside features
// These prevent regressions in authentication and authorization
describe('Authentication security', () => {
it('rejects expired JWT tokens', async () => {
const expiredToken = generateToken({ userId: 1 }, { expiresIn: '-1s' });
const res = await request(app)
.get('/api/profile')
.set('Authorization', `Bearer ${expiredToken}`)
.expect(401);
expect(res.body.error).toBe('Token expired');
});
it('rejects tampered JWT tokens', async () => {
const token = generateToken({ userId: 1 });
const tampered = token.slice(0, -5) + 'xxxxx';
await request(app)
.get('/api/profile')
.set('Authorization', `Bearer ${tampered}`)
.expect(401);
});
it('prevents IDOR: user cannot access other users data', async () => {
const user1Token = await loginAs('[email protected]');
await request(app)
.get('/api/users/user2-id/orders')
.set('Authorization', `Bearer ${user1Token}`)
.expect(403);
});
it('rate limits login attempts', async () => {
for (let i = 0; i < 10; i++) {
await request(app).post('/api/login').send({ email: 'x', password: 'x' });
}
const res = await request(app).post('/api/login').send({ email: 'x', password: 'x' });
expect(res.status).toBe(429); // Too Many Requests
});
});