security-review
Security Review
Evaluate your application's security posture against industry standards and validate findings through browser-based penetration testing. This review covers the attack surface that static analysis tools miss — runtime behavior, header configuration, authentication flows, and client-side vulnerabilities.
When to use
Use /security-review when:
- Before launching a new application or feature
- After adding authentication or authorization changes
- When handling sensitive data (user credentials, payment info, PII)
- Preparing for a security audit
- After a security incident to check for similar issues
- Reviewing third-party integrations
Standards Referenced
- OWASP Top 10 (2021) — Top web application security risks
- OWASP ASVS v4.0 — Application Security Verification Standard
- OWASP Session Management Cheat Sheet
- NIST 800-63B — Digital Identity Guidelines (authentication)
- CWE/SANS Top 25 — Most Dangerous Software Weaknesses
- Mozilla Observatory — HTTP security header best practices
Phase Overview
Phase 1: EDUCATE → Security context and what we check
Phase 2: SCOPE → Identify attack surface, auth mechanisms, data flows
Phase 3: ANALYZE → Automated checks + browser-based penetration testing
Phase 4: REPORT → Findings with evidence, CVE references, confidence scores
Phase 5: REMEDIATE → Fix guidance + YAML regression tests
Phase 1: Educate
Why this matters: The average cost of a data breach is $4.45M (IBM 2023). 83% of web applications have at least one critical vulnerability. Many security issues are only detectable at runtime — misconfigured headers, insecure token storage, broken access controls — which is exactly what browser-based testing catches.
This review checks your app against objective security criteria with browser-based validation. Every finding references a specific standard (OWASP, CWE, NIST).
Phase 2: Scope
Gather context
-
Auto-detect from codebase:
- Authentication mechanism (JWT, sessions, OAuth, API keys)
- Framework security features in use (CSRF tokens, CORS config, CSP)
- Dependencies with known vulnerabilities (
npm audit/pip audit) - API routes and endpoints
- Environment variable handling
- File upload capabilities
- Third-party scripts and CDN usage
-
Ask the user (one at a time):
- Target URL: Where is the app running?
- Auth mechanism: How do users log in? (auto-detected, confirm)
- Test credentials: Do you have test accounts I can use? (needed for authenticated testing)
- Sensitive data: What sensitive data does the app handle? (PII, payments, health records)
- Known concerns: Any specific areas you're worried about? (optional)
-
Map the attack surface:
- List all user input points (forms, URL params, file uploads, WebSocket messages)
- List all API endpoints with their auth requirements
- List all third-party integrations
- Identify data flow: where does sensitive data enter, process, store, and exit?
Phase 3: Analyze
Open a browser session with new_session using record_evidence: true. Run all applicable check categories.
Category A: HTTP Security Headers (HDR)
| Check ID | Check | Standard | Method |
|---|---|---|---|
| HDR-01 | Content-Security-Policy header present and restrictive | OWASP A05 | Inspect response headers |
| HDR-02 | Strict-Transport-Security (HSTS) with long max-age | OWASP Transport | Check header presence and value |
| HDR-03 | X-Content-Type-Options: nosniff | Mozilla Observatory | Check header |
| HDR-04 | X-Frame-Options or CSP frame-ancestors | OWASP Clickjacking | Check header |
| HDR-05 | Referrer-Policy set appropriately | Privacy/Security | Check header value |
| HDR-06 | Permissions-Policy restricts sensitive APIs | Browser security | Check camera, microphone, geolocation policies |
| HDR-07 | No Server/X-Powered-By version disclosure | Information leak | Check for version strings in headers |
| HDR-08 | Cache-Control for sensitive pages | OWASP Session | Check no-store for authenticated content |
| HDR-09 | CORS not overly permissive | OWASP A05 | Check Access-Control-Allow-Origin |
| HDR-10 | No mixed content (HTTP resources on HTTPS page) | Transport security | Inspect all resource URLs |
Browser validation: Use JavaScript via act to inspect document.querySelector('meta[http-equiv]') and fetch response headers via a same-origin request. Use get_browser_console_logs to check for mixed content warnings.
Category B: Authentication & Session Management (AUTH)
| Check ID | Check | Standard | Method |
|---|---|---|---|
| AUTH-01 | Tokens not stored in localStorage | OWASP ASVS 3.3.2 | Check localStorage/sessionStorage for tokens |
| AUTH-02 | Session cookies have HttpOnly flag | OWASP Session | Inspect Set-Cookie headers |
| AUTH-03 | Session cookies have Secure flag | OWASP Session | Inspect Set-Cookie headers |
| AUTH-04 | Session cookies have SameSite attribute | OWASP CSRF | Inspect Set-Cookie headers |
| AUTH-05 | Session expires after idle timeout | OWASP ASVS 3.3.1 | Wait and verify session invalidation |
| AUTH-06 | Logout invalidates server-side session | OWASP ASVS 3.3.1 | Logout, replay old token, check response |
| AUTH-07 | Password reset tokens are single-use | OWASP Auth | Use reset link twice, verify second fails |
| AUTH-08 | No credentials in URL parameters | OWASP Transport | Check URL for tokens/passwords |
| AUTH-09 | Brute force protection on login | OWASP Auth | Attempt multiple failed logins, check for lockout/rate-limit |
| AUTH-10 | CSRF protection on state-changing requests | OWASP A01 | Submit forms without CSRF token |
| AUTH-11 | JWT signature verified (if applicable) | OWASP Auth | Send modified JWT, check rejection |
| AUTH-12 | OAuth state parameter used (if applicable) | OWASP Auth | Check OAuth flow for state param |
Browser validation: Log in via act, inspect cookies with JavaScript (document.cookie — HttpOnly cookies won't appear, which is correct). Check localStorage. Perform logout, replay requests. Attempt brute force (5 wrong passwords). Modify JWT tokens and test.
Category C: Input Validation & Injection (INJ)
| Check ID | Check | Standard | Method |
|---|---|---|---|
| INJ-01 | XSS: reflected input in page | OWASP A03 / CWE-79 | Submit <script>alert(1)</script> in all inputs, check if rendered |
| INJ-02 | XSS: stored input from database | OWASP A03 / CWE-79 | Submit script via form, check if rendered on subsequent page loads |
| INJ-03 | SQL injection in form inputs | OWASP A03 / CWE-89 | Submit ' OR '1'='1 patterns, check for errors |
| INJ-04 | Open redirect via URL parameters | CWE-601 | Test redirect params with external URLs |
| INJ-05 | Path traversal in file operations | CWE-22 | Test ../../etc/passwd in file-related params |
| INJ-06 | Command injection in input fields | CWE-78 | Test ; ls or ` |
| INJ-07 | HTML injection in user content | CWE-79 | Submit HTML tags, check if rendered |
| INJ-08 | URL scheme validation (javascript:) | CWE-79 | Test javascript:alert(1) in URL inputs |
| INJ-09 | File upload validation | OWASP A04 | Upload files with wrong extensions, oversized files, executable content |
| INJ-10 | API input validation | OWASP A03 | Send malformed JSON, missing fields, wrong types to API endpoints |
Browser validation: Use act to fill form fields with test payloads. Capture page state after submission. Check for script execution, error messages, unexpected behavior. Use get_browser_console_logs for JavaScript errors that indicate injection vectors.
Important: These are non-destructive test payloads for detection only. Do not attempt actual exploitation. Alert-based XSS tests use alert(1) which is harmless.
Category D: Access Control (AC)
| Check ID | Check | Standard | Method |
|---|---|---|---|
| AC-01 | Authenticated pages return 401/403 without auth | OWASP A01 | Access protected URLs without authentication |
| AC-02 | No IDOR (Insecure Direct Object Reference) | OWASP A01 / CWE-639 | Change resource IDs in URLs, check for unauthorized access |
| AC-03 | API endpoints enforce authorization | OWASP A01 | Call API endpoints with wrong/missing auth |
| AC-04 | Admin pages are not accessible to regular users | OWASP A01 | Navigate to admin routes with regular user session |
| AC-05 | No sensitive data in client-side source | Information leak | Check JavaScript bundles for API keys, secrets |
| AC-06 | Directory listing disabled | Information leak | Access directory URLs (e.g., /api/, /static/) |
| AC-07 | Debug endpoints not exposed in production | OWASP A05 | Check common debug paths (/debug, /trace, /graphql playground) |
| AC-08 | Error messages don't leak internal details | OWASP A05 | Trigger errors, check for stack traces, DB details |
Browser validation: Navigate to protected pages without auth. Try accessing resources belonging to other users. Check JavaScript source for hardcoded secrets using act with JavaScript to scan script contents.
Category E: Client-Side Security (CLI)
| Check ID | Check | Standard | Method |
|---|---|---|---|
| CLI-01 | No sensitive data in client-side storage | OWASP Storage | Inspect localStorage, sessionStorage, IndexedDB |
| CLI-02 | Subresource Integrity (SRI) on CDN resources | Supply chain | Check integrity attribute on external scripts/styles |
| CLI-03 | Third-party scripts inventory | Supply chain | List all external script sources |
| CLI-04 | No eval() or innerHTML with user input | CWE-79 | Scan JavaScript for dangerous patterns |
| CLI-05 | Service worker scope is restricted | Client security | Check SW registration scope |
| CLI-06 | WebSocket connections use WSS | Transport | Check WS connection URLs |
| CLI-07 | No sensitive data in console logs | Information leak | Check get_browser_console_logs output |
| CLI-08 | Clickjacking protection works | OWASP Clickjacking | Test embedding page in iframe |
Browser validation: Use JavaScript via act to enumerate localStorage keys, check script tags for SRI, list all network requests to external domains. Use get_browser_console_logs to check for leaked data.
Category F: Dependency & Supply Chain (DEP)
| Check ID | Check | Standard | Method |
|---|---|---|---|
| DEP-01 | No known vulnerable dependencies | OWASP A06 / CWE-1035 | Run npm audit / pip audit |
| DEP-02 | Lock file exists and is committed | Supply chain | Check for package-lock.json / yarn.lock / pnpm-lock.yaml |
| DEP-03 | No unnecessary dependencies | Attack surface | Check for unused packages |
| DEP-04 | CDN resources use SRI | Supply chain | Check integrity attributes (same as CLI-02) |
| DEP-05 | No typosquatting risk in dependencies | Supply chain | Check package names against known packages |
Validation: Run dependency audit commands. Cross-reference with codebase scan from Phase 2.
Phase 4: Report
Generate a structured report saved to shiplight/reports/security-review-{date}.md:
# Security Review Report
**Date:** {date}
**URL:** {url}
**Auth mechanism:** {type}
**Attack surface:** {summary}
## Overall Score: {X}/10 | Confidence: {X}%
## Score Breakdown
| Category | Score | Findings |
|----------|-------|----------|
| HTTP Headers (HDR) | 6/10 | 1 critical, 2 high |
| Auth & Sessions (AUTH) | 4/10 | 2 critical, 1 high |
| Input Validation (INJ) | 7/10 | 1 high, 2 medium |
| Access Control (AC) | 8/10 | 1 medium |
| Client-Side (CLI) | 5/10 | 1 critical, 1 high |
| Dependencies (DEP) | 9/10 | 1 low |
## Findings
### CRITICAL
#### AUTH-01: JWT stored in localStorage — XSS leads to full account takeover
- **Standard:** OWASP ASVS 3.3.2 / CWE-922
- **Finding:** Access token stored in `localStorage` under key `auth_token`, accessible to any XSS payload
- **Evidence:** [screenshot of Application > Storage showing JWT]
- **Attack scenario:** Any XSS vulnerability (even via third-party script) can exfiltrate all user tokens
- **CVSS estimate:** 8.1 (High)
- **Confidence:** 95%
...
Confidence Scoring
- 90-100%: Exploited and verified in browser (e.g., XSS payload executed, unauthorized access confirmed)
- 70-89%: Strong evidence from inspection (e.g., missing header confirmed, insecure cookie flags observed)
- 50-69%: Code-level evidence, not fully validated at runtime
- Below 50%: Don't report — too speculative
Phase 5: Remediate
For each finding, provide:
1. Fix guidance
#### AUTH-01: JWT stored in localStorage
**Risk:** Any XSS → full account takeover
**File:** src/lib/auth.ts:47
**Current:** `localStorage.setItem('auth_token', jwt)`
**Fix:** Move to HttpOnly cookie set by the server
- Server: `Set-Cookie: token=<jwt>; HttpOnly; Secure; SameSite=Strict; Path=/`
- Client: Remove all localStorage token operations
- API calls: Cookies sent automatically (remove Authorization header)
**Migration steps:**
1. Add cookie-setting endpoint on server
2. Update API middleware to read from cookie
3. Remove client-side token storage
4. Update CORS to allow credentials
2. YAML regression test
- name: auth-01-no-tokens-in-localstorage
description: Verify authentication tokens are not stored in localStorage
severity: critical
standard: OWASP-ASVS-3.3.2
steps:
- URL: /login
- intent: Enter test username
action: fill
locator: "getByLabel('Email')"
value: "test@example.com"
- intent: Enter test password
action: fill
locator: "getByLabel('Password')"
value: "testpass123"
- intent: Click login button
action: click
locator: "getByRole('button', { name: 'Sign in' })"
- WAIT_UNTIL: User is logged in and dashboard is visible
timeout_seconds: 15
- CODE: |
const keys = Object.keys(localStorage);
const tokenKeys = keys.filter(k =>
/token|jwt|auth|session|access/i.test(k)
);
if (tokenKeys.length > 0) {
throw new Error(
`Auth tokens found in localStorage: ${tokenKeys.join(', ')}`
);
}
- VERIFY: No authentication tokens are stored in browser localStorage
Save all YAML tests to shiplight/tests/security-review.test.yaml.
Penetration Test Depth Levels
--quick: Headers (HDR) + Cookie flags (AUTH-02/03/04) + localStorage check (AUTH-01) + dependency audit (DEP-01). ~2 minutes.- default: All categories, standard payloads. ~10 minutes.
--thorough: All categories + extended injection payloads + IDOR enumeration + brute force testing + full third-party script analysis. ~20-30 minutes.
Tips
- Always use test credentials, never production credentials
- XSS test payloads are non-destructive (
alert(1)) — safe for staging environments - For authenticated testing, save the session with
save_storage_stateafter login - Run
npm auditbefore the browser-based review to catch known CVEs early - Use
get_browser_console_logs— many security issues produce console warnings - Close the session with
close_sessionand usegenerate_html_reportfor evidence
More from shiplightai/agent-skills
verify
Verify UI changes in the browser using Shiplight MCP tools.
92compliance-review
Compliance review and testing: evaluate your application against HIPAA, SOC 2, PCI-DSS, and GDPR technical requirements with browser-based validation and YAML regression tests for continuous compliance.
89privacy-review
Privacy review and testing: evaluate PII handling, data flows, tracking inventory, consent mechanisms, storage practices, and data leakage risks with browser-based validation against GDPR, CCPA, and industry best practices.
87geo-review
Generative Engine Optimization review: evaluate your content's visibility to AI-powered search engines — citation-worthiness, content structure, authority signals, llms.txt, entity clarity, and AI retrieval readiness.
87resilience-review
Resilience review and testing: evaluate error handling, graceful degradation, API contract compliance, edge cases, and failure recovery with browser-based fault injection and validation.
87cloud
Sync local tests with Shiplight cloud — push and pull YAML test cases, templates, and functions between your repo and the cloud. Requires a Shiplight cloud subscription.
86