JavaScript / TypeScript Bug Bounty Analysis Skill
Advanced static analysis of JS/TS codebases for high-severity vulnerabilities.
Rule #1: NO HALLUCINATION. Every finding MUST be backed by concrete code evidence (file path + line + snippet) and a clear, reproducible exploit path.
Rule #2: Taint Analysis is MANDATORY. Prove how user-controlled input (Source) reaches the dangerous function (Sink) without proper sanitization.
Rule #3: BRAIN DUMP MANDATE. Before listing vulnerabilities, document your reasoning, searches performed, dead ends, and false positive eliminations.
Rule #4: Use Claude Code tools. Prefer Grep tool over rg via Bash. Use Glob for file discovery. Reserve Bash for ast-grep (sg) and npm audit only.
Tool Usage
Subagents MUST use Claude Code's dedicated tools:
- Grep tool for all text/pattern searches (NOT
rgorgrepvia Bash) - Glob tool for file discovery (NOT
findorlsvia Bash) - Read tool for file reading (NOT
cat/head/tailvia Bash) - Bash tool ONLY for:
ast-grep(sg) commands,nodescripts,npm audit
Available scripts (invoke via Bash):
scripts/analyze.js --target <path> --category <cat>— ast-grep scanner by vulnerability categoryscripts/check_safety.js --target <domain> --platform <name>— Safe Harbor verificationscripts/pattern_validator.js --patterns-dir <dir> --fixtures-dir <dir>— validate ast-grep patterns
Directory Exclusions
ALL searches MUST exclude: node_modules/, dist/, build/, .next/, .nuxt/, coverage/, .git/, vendor/, __pycache__/, .cache/, .turbo/
Use Grep tool's glob parameter to filter (e.g., glob: "!node_modules/**"), or target specific source directories.
Phase 0: Setup & Detection
Goal: Prepare workspace, detect framework, check for exposed files.
mkdir -p .js-audit
- Read
package.json— identify framework, dependencies, scripts - Detect framework:
- Express (
express), Next.js (next), NestJS (@nestjs/core), Fastify (fastify) - Hono, Koa, Nuxt, SvelteKit, Remix, Astro
- Express (
- Check TypeScript: presence of
tsconfig.json - Check monorepo:
lerna.json,pnpm-workspace.yaml,turbo.json,nx.json - Exposed sensitive files (use Glob):
.env*,*.pem,*.key,firebase*.json,serviceAccount*.json,google-services.json- Flag any NOT listed in
.gitignore
Subagent Orchestration
Delegate to 3 parallel subagents, then compile report.
Wave 1 (PARALLEL — launch all three in a single message):
├── js-security-expert agent → Phases 1 + 2 + 3 (Recon + Frontend + Auth/Session)
├── api-security agent → Phases 4 + 5 (Dangerous Sinks + Injection Flaws)
└── webapp-security agent → Phase 6 (Logic, DOM XSS, File Upload, Config)
Wave 2 (SEQUENTIAL — after Wave 1 completes):
└── report-writer agent → Phase 7: Taint validation + compile .js-audit/report.md
Steps:
- Phase 0: Run setup yourself (NOT delegated).
- Wave 1: Launch three
Agentcalls in parallel. Provide each with:- Full phase instructions for their assigned phases
- Codebase path and detected framework from Phase 0
references/vulnerability-patterns.mdfor pattern matchingreferences/cwe-checklist.mdfor CWE mapping
- Wave 2: Launch
report-writerwith all findings +references/escalation-guide.mdfor chain identification. - Output
.js-audit/report.mdpath.
Workflow Overview
Phase 1: Recon & Routing → Routes, endpoints, Server Actions, middleware chain
Phase 2: Frontend-to-Backend → SSRF vectors, postMessage, WebSocket, CORS, state encoding
Phase 3: Auth & Session → JWT, OAuth2, CSRF, cookies, session, IDOR, rate limiting
Phase 4: Dangerous Sinks → RCE, SSRF, SSTI, Deserialization, Path Traversal
Phase 5: Injection Flaws → SQLi, NoSQLi, GraphQL, ReDoS, SSR XSS
Phase 6: Logic & Client-side → Prototype Pollution, Mass Assignment, DOM XSS, File Upload, Config
Phase 7: Taint & Report → Source-to-Sink validation + report generation
Phase 1: Recon & Routing (Attack Surface Mapping)
Goal: Identify where user input enters the application (Sources).
Search patterns (use Grep tool, targeting source directories only):
-
Express.js:
app\.(get|post|put|delete|patch|all|use)\(router\.(get|post|put|delete|patch|all|use)\(
-
Next.js (Pages Router + App Router + Server Actions):
export (async )?function (GET|POST|PUT|DELETE|PATCH)(App Router)export default function handler(Pages Router)"use server"(Server Actions)getServerSideProps,getStaticProps
-
NestJS:
@(Get|Post|Put|Delete|Patch|All)\((route decorators)@Controller\(,@Injectable\(
-
Fastify:
fastify\.(get|post|put|delete|patch)\(
-
User input sources (ALL frameworks):
req\.(body|query|params|headers|cookies|files|file)ctx\.(request|params|query|body)@Body\(\),@Query\(\),@Param\(\),@Headers\(\)(NestJS decorators)
-
Middleware chain (auth bypass vector):
app\.use\(,router\.use\(- Map ORDER of middleware — auth applied AFTER route handler registration = bypass
- Check for
next()called without auth validation
For each route, document:
- Endpoint path, HTTP method, input sources
- Auth middleware applied? Rate limited?
Phase 2: Client-to-Server Surface Mapping
Goal: Discover hidden attack surfaces from frontend-backend interaction.
Search patterns:
-
SSRF vectors (frontend passing URLs to backend):
fetch\(with variable URL arguments (not string literals)axios\.(get|post|put|request)\(with variable arguments- Parameters named:
url,target,path,endpoint,webhook,callback,redirect,proxy,dest
-
postMessage vulnerabilities (deep analysis):
addEventListener\(["']message["']— find ALL handlers, then for each:- Origin validation: Is
event.originchecked against a HARDCODED allowlist? Domain-only checks are insufficient - Inverted origin check:
trusted.isSameOrigin(untrusted)instead ofuntrusted.isSameOrigin(trusted)— when trusted has null fields (sandboxed iframe), all checks pass trivially - Regex domain bypass:
/^https:\/\/.*facebook\.com$/— unescaped dot allowsevilfacebook.com. Check for\.before domain names - Origin stored as trusted:
event.originstored to localStorage/variable then used to constructscript.src,iframe.src, orfetch()URLs = critical - DOM injection from message:
innerHTML,outerHTML,document.write(),.html(),form.actionset fromevent.data= DOM XSS even with valid origin - API/OAuth request construction: Message body fields used to build server requests or OAuth parameters = parameter injection
- Type confusion: Value expected as String but Array/Object accepted —
typeof x === "string"gate bypassed by arrays with malicious.toString()
- Origin validation: Is
postMessage\(.*,\s*['"]?\*['"]?\)— wildcard targetOrigin with sensitive data (tokens, codes, blobs) = ALWAYS a bugnew MessageChannel— after port sharing, verify subsequent messages on channel still authenticate; port reuse without re-validation = hijackMath\.random\(\)used to generate tokens, nonces, or shared secrets for cross-window auth — predictable via V8 PRNG state reconstruction (Z3 solver with 4+ sequential outputs)window\.name— persists across navigations; leaks data cross-origin if iframe navigated to attacker domain
-
WebSocket:
new WebSocket\(,io\(,io\.connect\(,socket\.on\(- Auth on WS connection? TLS (
wss://notws://)? Message validation?
-
CORS misconfiguration:
Access-Control-Allow-Origin— search for*or reflected origincors\(config — checkorigin:andcredentials:combinationres\.(header|setHeader)\(.*Access-Control
-
State encoding (deserialization vectors):
btoa\(JSON\.stringify\(,Buffer\.from\(.*base64JSON\.parse\(atob\(,JSON\.parse\(Buffer\.from\(
-
Hidden/admin routes:
\/api\/(internal|admin|debug|test|_|health|metrics|graphql|graphiql)window\.location\.(origin|href|hash)— open redirect sources
-
Client-Side Path Traversal (CSPT2CSRF):
fetch('/api/' + userInput + '/action')— path traversal via../in userInputfetch(`/api/${param}/data`)— template literal URL path with unsanitized inputaxios.get(baseUrl + variable + '/endpoint')— concatenated path segments- Sources:
location.hash,location.search,URLSearchParams.get(), database-injected values - Sinks:
fetch(),axios.*(),XMLHttpRequest.open()with user input in URL path - Impact: GET CSPT → data leak; POST/PUT/DELETE CSPT → state-changing CSRF (bypasses SameSite cookies since request is same-origin)
- Chain: GET CSPT leaks JSON with ID → that ID fed into POST CSPT for state change
Phase 3: Auth & Session Flaws
Goal: Find authentication bypass, session management flaws, broken access control.
Search patterns:
-
JWT issues:
jwt\.sign\(— hardcoded secret? weak algorithm?jwt\.verify\(—algorithmsoption set? (prevents algorithm confusion)jwt\.decode\(— decode WITHOUT verify = no signature check (WARNING)algorithm.*none— "none" algorithm attack
-
OAuth2 misconfiguration:
redirect_uri,callback_url— validated against allowlist?stateparameter — generated and checked? (CSRF in OAuth flow)client_secretin frontend code = leaked secret- PKCE:
code_verifier,code_challengepresent for public clients?
-
Session & cookies:
- Cookie config: check
httpOnly,secure,sameSiteflags express-sessionconfig: hardcodedsecret?resave?saveUninitialized?- Session regeneration after login? (
req.session.regenerate)
- Cookie config: check
-
CSRF protection:
csrf,csurf,csrf-csrfin dependencies?SameSitecookie attribute?- State-changing endpoints (POST/PUT/DELETE) without CSRF token
-
IDOR / Broken Access Control:
- DB queries using
req.params.idwithout ownership check (WHERE owner = req.user.id) findById,findOne,findByPkwith user-controlled ID- Role checks:
req.user.role,req.body.role— can role be tampered client-side?
- DB queries using
-
Rate limiting:
express-rate-limit,rate-limiter-flexiblein dependencies?- Auth endpoints (
/login,/register,/forgot-password) without limiting = brute force
-
Password reset:
- Token: cryptographically random? sufficient length? expiry set?
- Can same token be reused after password change?
-
OAuth redirect_uri bypass patterns:
redirect_uri.startsWith(registeredCallback)— bypassed with../path traversal (e.g.,/callback/../../open_redirect?next=evil.com)new URL(redirect_uri).hostname === allowedHostwithout.pathnamecheck — attacker uses path to open redirect on allowed domainfallback_redirect_uriorbase_uriparameters with domain-only validation — path component not checked- Redirect target read from cookie/storage:
res.redirect(req.cookies.redirect_url)— attacker pre-sets cookie via CSRF response_type=tokenwith redirect chains — token persists in URL fragment across HTTP redirects through subdomains- HTTP Parameter Pollution:
param[0=valueoverridingparam=valueserver-side; testredirect_uri[0=evil.comalongside legitimateredirect_uri - Login CSRF as chain enabler: Endpoints accepting session swap without CSRF token enable attack chains (force victim into attacker session, then exploit OAuth/linking flows)
-
GraphQL-specific authorization:
- Multiple
doc_idvalues for same resource type — edit/mutation doc_id may expose private fields (linked_*,shadow_*,internal_*) not visible in view doc_id actor_idoruser_idin mutation variables NOT validated against the authenticated session — spoofable- GraphQL error messages leaking internal type names:
"No such class: OBJECT_TYPE"in production - Batch API with result interpolation:
{result=NAME:$.field}enables cross-request data exfiltration - Mutations accepting
application/x-www-form-urlencoded(form-submittable) = CSRF without token
- Multiple
Phase 4: Dangerous Sinks (RCE, SSRF, SSTI, Path Traversal)
Goal: Find functions that execute code, make requests, or access files.
Use BOTH Grep tool AND ast-grep (sg via Bash):
-
RCE / Command Injection:
- Grep:
child_process,exec\(,execSync\(,spawn\(,spawnSync\( - Grep:
eval\(,new Function\(,vm\.runIn,vm\.createScript - sg:
exec($CMD),exec(\$CMD`),execSync($CMD),spawn($CMD, $$)` - sg:
eval($CODE),new Function($CODE) - sg:
setTimeout($STR, $$)— string argument (not function) = eval-like
- Grep:
-
SSRF:
- Grep:
fetch\(,axios,got\(,request\(,http\.get\(,https\.request\(,undici - sg:
fetch($URL),fetch($URL, $$),axios.get($URL),axios($CFG) - sg:
got($URL),http.get($URL)
- Grep:
-
Deserialization:
unserialize\(,node-serializeyaml\.load\((js-yaml withoutsafeLoad/SAFE_SCHEMA)JSON\.parse\(with user input flowing to prototype-sensitive operations
-
SSTI:
res\.render\(.*req\.(body|query|params)— user input in template contextejs\.render\(,pug\.compile\(,handlebars\.compile\(,nunjucks\.renderString\(- Template string with user input in response:
res.send(`...${req.query.x}...`)
-
Path Traversal / LFI:
fs\.readFile,fs\.readFileSync,fs\.createReadStream— check if path includes user inputfs\.writeFile,fs\.writeFileSync— arbitrary file writepath\.join\(orpath\.resolve\(with user-controlled segments without../checkres\.sendFile\(,res\.download\(
Critical: If ANY sink receives user input via interpolation or concatenation WITHOUT validation → Critical finding.
Phase 5: Injection Flaws
Goal: SQL/NoSQL injection, GraphQL abuse, ReDoS, SSR XSS.
-
SQL Injection:
- Template literals in queries:
query(`SELECT ... WHERE id = ${id}`) - String concat:
"SELECT * FROM " + table - Grep:
\.query\(,\.raw\(,knex\.raw\(,sequelize\.query\(,prisma\.\$queryRaw - sg:
$DB.query(\$SQL`),knex.raw($SQL)` - Exclude safe patterns: parameterized queries (
$1,?,:nameplaceholders)
- Template literals in queries:
-
NoSQL Injection (MongoDB/Mongoose):
\.(find|findOne|updateOne|deleteOne)\(.*req\.(body|query)- User objects in query enabling
{ $ne: null },{ $regex: ".*" }operators - Fix check:
mongo-sanitize,String()casting, explicit$eq
-
GraphQL:
- Introspection enabled:
introspection:\s*trueor not explicitly disabled - Missing depth/complexity limits: check for
graphql-depth-limit,graphql-query-complexity - Batching: multiple ops in single request without limit
- Resolver auth: auth checks in resolvers (not just middleware)?
- Field suggestion leak:
Did you meanin error responses
- Introspection enabled:
-
ReDoS (Regex Denial of Service):
new RegExp\(with user-controlled pattern = arbitrary ReDoS- Nested quantifiers in hardcoded regex:
(a+)+,(a|a)*,([a-z]+)* - Grep:
\.match\(,\.replace\(,\.test\(with dynamic regex argument
-
SSR XSS:
dangerouslySetInnerHTMLwith user-derived datares\.send\(.*req\.(body|query|params)— unsanitized in response- Template engines rendering user input without escaping
-
Parser Differentials / MIME Confusion:
Content-Type: application/json;,text/html— server validator seesapplication/json, browser renderstext/html- "Validate then use original" anti-pattern:
if (parse(ct) === 'application/json') res.setHeader('Content-Type', ct)— MUST use parsed value, not original - Missing
X-Content-Type-Options: nosniff+ user-controlled Content-Type = MIME sniffing XSS res.setHeader('Content-Type', req.headers['content-type'])ortypeparameter reflected in response header- API responses echoing user input in JSON values without HTML encoding + missing Content-Type header = XSS via MIME sniffing (IE/Edge)
-
HTML-to-PDF / Server-Side Rendering SSRF:
- Libraries:
wkhtmltopdf,puppeteer,playwright,headless-chrome,phantom,pdf-libwith HTML input - User input in HTML passed to PDF converter →
<iframe src="file:///etc/passwd">for LFI <iframe src="http://169.254.169.254/...">for SSRF to cloud metadata- Double-decode vulnerability: input HTML-encoded at submission → HTML-decoded server-side before PDF render → sanitization undone
- Search:
pdf,render.*html,puppeteer,wkhtmltopdf,headlessin same code path as user input
- Libraries:
Phase 6: Logic Flaws, DOM XSS & Configuration
Goal: Prototype Pollution, Mass Assignment, DOM XSS, file upload, configuration.
-
Prototype Pollution:
Object\.assign\(.*req\.body,\.merge\(,lodash\.merge,_.merge,_.defaultsDeep- Deep merge/clone with user-controlled keys (
__proto__,constructor,prototype) - sg:
Object.assign($T, req.body),_.merge($T, $S)
-
Mass Assignment:
- ORM create/update with raw request body:
- sg:
$M.create(req.body),$M.update(req.body, $$) - Sequelize without
fieldswhitelist, Mongoose without strict schema - Prisma:
prisma.$M.create({ data: req.body })
-
DOM-based XSS (frontend):
- Sources:
location\.(search|hash|href),document\.referrer,document\.URL,window\.name - Sinks:
\.innerHTML,\.outerHTML,document\.write,eval\(,jQuery\.html\(,\$\(.*\)\.html\( dangerouslySetInnerHTMLwith user-derived data (React)v-html(Vue),[innerHTML](Angular) with dynamic binding
- Sources:
-
File upload:
multer,formidable,busboy— check:- File type validation (extension AND mime type)?
- Filename sanitization (path traversal via
../)? - File size limits set?
- Storage destination (public accessible directory?)
-
Environment & config exposure:
.envfiles committed (check.gitignore)process\.envvalues leaked to client-side bundle- Next.js:
NEXT_PUBLIC_prefix exposes vars to client — audit what's prefixed - Debug mode in production:
NODE_ENV.*development,DEBUG= - Source maps:
*.mapfiles in build output
-
Race conditions:
- Check-then-act without transaction: read balance → check → update
- Missing
awaiton critical async operations Promise.allon dependent operations that should be sequential
-
XS-Leaks (Cross-Site Information Leaks):
- CORB oracle: Endpoint returns different Content-Type based on user state →
onload/onerrordifference in<script>tag reveals state - X-Frame-Options oracle: Conditionally applied
X-Frame-Options: denybased on user-supplied params (__user) → timing/behavioral difference reveals identity - Prototype pollution as XS-Leak gadget: Globally-loaded scripts calling
export default { userId }— polluteFunction.prototype.default/.__esModulebefore script loads to intercept user ID - Error-vs-success oracle: Same endpoint returning different HTTP status codes for "data exists" vs "no data" based on target object ID
- Detection: Look for any cross-origin-loadable resource whose behavior (load/error, timing, status) varies based on authentication state
- CORB oracle: Endpoint returns different Content-Type based on user state →
-
Supply-chain XSS via shared scripts:
- Analytics/pixel scripts (
fbevents.js,capig-events.js) served across many domains - User-controlled values string-concatenated into JavaScript code generation on the server = stored XSS on ALL sites loading the script
- Search: string builders/template literals producing
.jsfile content with user-derived values - Any gateway/config endpoint generating JavaScript from database values without escaping
- Analytics/pixel scripts (
-
Debug code in production:
document.writein OAuth callback scripts with debug flag enabled- Debug endpoints (
/debug/,/_debug/,?debug=true) returning sensitive data console.logwith sensitive variables (tokens, passwords) in production builds- Source maps (
*.map) exposing original source
Phase 7: Taint Analysis & Report Generation
MANDATORY Taint Analysis for every finding:
- Source: Where user input enters (e.g.,
req.query.url) - Propagator: How it flows (assignments, function calls, transformations)
- Sanitizer check: Validation present? (allowlist, type cast, library sanitizer)
- Sink: Where it reaches a dangerous function (e.g.,
fetch(url)) - Verdict: Unbroken flow without strict validation = vulnerability
References for report-writer agent:
references/cwe-checklist.mdfor CWE mapping and CVSS scoringreferences/escalation-guide.mdfor identifying attack chain escalationsreferences/h1-examples.mdfor real-world precedentreferences/writeup-insights.mdfor real-world postMessage, OAuth, CSPT, parser differential, and XS-Leak patterns from Meta/Facebook bug bounty writeups
Output: Write .js-audit/report.md
Report Template
# Brain Dump
## Project Overview
- **Framework:** [detected from Phase 0]
- **Language:** JS / TS
- **Entry points:** [count of routes/endpoints]
- **Auth mechanism:** [JWT / session / OAuth / none]
- **Key dependencies:** [security-relevant packages]
## Attack Surface Summary
- **Routes without auth:** [list]
- **Dangerous sinks found:** [count by type]
- **External integrations:** [APIs, databases, cloud services]
## Analysis Log
- [Key decisions, patterns investigated, reasoning]
- [Interesting code paths and potential attack chains]
## Dead Ends & False Positive Elimination
- [Sinks found but properly validated/sanitized — with explanation]
- [Patterns searched but not present in this codebase]
- [Findings investigated and discarded — specific reason]
---
# JS/TS Bug Bounty Report: [Project Name]
## Executive Summary
- **Findings:** N total | Critical: X | High: X | Medium: X | Low: X
- **Framework:** [detected]
- **Key Risks:** [1-2 sentences]
---
## [VULN-001] Title — SEVERITY
**CWE:** CWE-XXX — [Title]
**CVSS 3.1:** X.X (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N)
**Impact:** [Concrete impact]
### Evidence & Taint Analysis
**Source:** `src/routes/api.ts:15`
```typescript
const userUrl = req.query.url; // SOURCE
Propagator: (if intermediate processing exists)
Sink: src/routes/api.ts:20
const response = await fetch(userUrl); // SINK — no validation
Flow: req.query.url → userUrl → fetch(userUrl) — unvalidated
Exploit PoC
GET /api/proxy?url=http://169.254.169.254/latest/meta-data/ HTTP/1.1
Host: target.com
Remediation
// Specific fix with secure code example