Security Testing Guide

Testing Approach Comparison

TypeFull NameHow It WorksBest Tools
SASTStatic Application Security TestingAnalyzes source code without running itSemgrep, CodeQL, Checkmarx, SonarQube
DASTDynamic Application Security TestingAttacks running application (black-box)OWASP ZAP, Burp Suite, Nuclei
IASTInteractive Application Security TestingAgent inside app monitors during testingContrast Security, Seeker
SCASoftware Composition AnalysisScans dependencies for known CVEsSnyk, Dependabot, OWASP Dependency-Check
Secret ScanningDetect credentials in code/historyPattern matching in git history and filesGitLeaks, 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 }); });